Этот код написан с целью самообучения. Чтоб закрепить материал я решил немного прокомментировать проделанную работу.
Сразу скажу: на компилируемых языках не писал.
[к] — клиент
[c] — сервер
1. По установленному TCP соединению, [к] передает публичный ключ rsa.
2. При помощи принятого публичного ключа, [c] шифрует и отправляет сообщения [к]
3. [к] расшифровывает и выводит сообщения.
Вот как это выглядит в консоли:
Импортируем нужные пакеты
К примеру:
Если вы не уверены в том какое имя пакета вам нужно то можно посмотреть на исходники пакета на самую верхнюю сточку кода.
К примеру для crypto/rsa Шестая строчка rsa.go
При помощи команды goinstall вы сможете установить пакеты от других разработчиков.
В этом случае вы будите импортировать что-то типа этого «bitbucket.org/user/project», «github.com/user/project» или «project.googlecode.com/hg»
Объявим нужные нам константы
Для того чтоб держать вместе соединение «с» и ключ от этого соединения «pubK» объявим тип данных remoteConn как структуру:
net.TCPConn — тип данных, который содержит структуру информации о TCP соединении.
rsa.PublicKey — тип данных. Нужен для зашифровки передаваемых сообщений.
В целях ознакомления будем обрабатывать возникающие ошибки таким образом:
Функция принимает одно значение
В данном случае мы работаем с типом Error из пакета os (
Объявим глобальную переменную
«Port: 0» в данном случае означает — любой свободный порт.
Следующая функция объединяет, соединение и публичный ключ для шифрования этого соединения в структуру
Причём возвращает ссылку на
Функция принимает ссылку на буфер (
Следующая функция является методом для ссылки на переменную типа
Проделывает ряд действий для зашифровки и отправки сообщения
Ниже функция которая оперирует ранее объявленными функциями и в конечном итоге отправляет «клиенту» название сервера и приветствия на разных языках.
На этом рассмотрение сервера окончено
Исходники без единого комментария можно найти тут:
code.google.com/p/learning-go-language/source/browse
Сразу скажу: на компилируемых языках не писал.
Что делает приложение
[к] — клиент
[c] — сервер
1. По установленному TCP соединению, [к] передает публичный ключ rsa.
2. При помощи принятого публичного ключа, [c] шифрует и отправляет сообщения [к]
3. [к] расшифровывает и выводит сообщения.
Вот как это выглядит в консоли:
Сервер
package main
Импортируем нужные пакеты
import(
// Пакет для ввода/вывода данных. В данном случае из консоли
"fmt"
// Пакет для передачи информации по Unix networks sockets, including TCP/IP, UDP протоколам.
// В данном случае будем использовать TCP протокол.
"net"
// Пакет для кроссплатформенной взаимодействия с операционной системой
"os"
// Реализует буфер ввода/вывода
"bufio"
// При помощи этого пакета будем шифровать и дешифровать передаваемую информацию
"crypto/rsa"
// Пакет для кроссплатформенной генерации случайных чисел
"crypto/rand"
// Для создания хешей методом sha1
"crypto/sha1"
// Для конвертации строковых данных в основные типы данных и обратно
"strconv"
// Для работы с большими числами
"big"
)
Дальнейшее обращение к пакету будет происходить по его имени через точку.К примеру:
fmt
.Println()
, os
.Exit()
и т.д.Если вы не уверены в том какое имя пакета вам нужно то можно посмотреть на исходники пакета на самую верхнюю сточку кода.
К примеру для crypto/rsa Шестая строчка rsa.go
При помощи команды goinstall вы сможете установить пакеты от других разработчиков.
В этом случае вы будите импортировать что-то типа этого «bitbucket.org/user/project», «github.com/user/project» или «project.googlecode.com/hg»
Объявим нужные нам константы
const(
// Используемый tcp протокол
tcpProtocol = "tcp4"
// Длина генерируемого rsa ключа
keySize = 1024
// Максимальная длина шифруемого сообщения в байтах
readWriterSize = keySize/8
)
Для того чтоб держать вместе соединение «с» и ключ от этого соединения «pubK» объявим тип данных remoteConn как структуру:
type remoteConn struct {
c *net.TCPConn
pubK *rsa.PublicKey
}
Звёздочка "*", перед типом переменной, означает что переменная является ссылкой на данные объявленного типаnet.TCPConn — тип данных, который содержит структуру информации о TCP соединении.
rsa.PublicKey — тип данных. Нужен для зашифровки передаваемых сообщений.
В целях ознакомления будем обрабатывать возникающие ошибки таким образом:
Функция принимает одно значение
err
у которого тип os.Error.В данном случае мы работаем с типом Error из пакета os (
os.Error
).func checkErr(err os.Error){
if err != nil {
// Выводим текст ошибки
fmt.Println(err)
// Завершаем программу
os.Exit(1)
}
}
Объявим глобальную переменную
listenAddr
которая будет ссылкой на структуру типа net.TCPAddrvar listenAddr = &net.TCPAddr{IP: net.IPv4(192,168,0,4), Port: 0}
Амперсанд "&" перед net.TCPAddr вернёт ссылку на этот тип.«Port: 0» в данном случае означает — любой свободный порт.
Следующая функция объединяет, соединение и публичный ключ для шифрования этого соединения в структуру
remoteConn
. Причём возвращает ссылку на
remoteConn
а не значение.func getRemoteConn(c *net.TCPConn) *remoteConn{
return &remoteConn{c: c, pubK: waitPubKey(bufio.NewReader(с))}
}
bufio.NewReader(с)
— создает буфер байт от соединения «c». Тип возвращаемых данных *bufio.Reader
(ссылка на bufio.Reader
)waitPubKey()
— ожидает от «клиента» когда тот в определённой последовательности передаст PublicKey
Функция принимает ссылку на буфер (
*bufio.Reader
) который в свою очередь содержит все байты пришедшие от соединение «c»// Вернёт ссылку на структуру данных rsa.PublicKey
func waitPubKey(buf *bufio.Reader) (*rsa.PublicKey) {
// Читаем строку из буфера
line, _, err := buf.ReadLine(); checkErr(err)
// Так как тип line - []byte (срез байт)
// то для удобства сравнения переконвертируем <code><b>line</b></code> в строку
if string(line) == "CONNECT" {
// Далее мы будем читать буфер в том же порядке, в котором отправляем данные с клиента
line, _, err := buf.ReadLine(); checkErr(err) // Читаем PublicKey.N
// Создаём пустой rsa.PublicKey
pubKey := rsa.PublicKey{N: big.NewInt(0)}
// pubKey.N == 0
// тип pubKey.N big.Int http://golang.org/pkg/big/#Int
// Конвертируем полученную строку и запихиваем в pubKey.N big.Int
pubKey.N.SetString(string(line), 10)
// Метод SetString() получает 2 параметра:
// string(line) - конвертирует полученные байты в строку
// 10 - система исчисления используемая в данной строке
// (2 двоичная, 8 восьмеричная, 10 десятичная, 16 шестнадцатеричная ...)
// Читаем из буфера второе число для pubKey.E
line, _, err = buf.ReadLine(); checkErr(err)
// Используемый пакет strconv для конвертации тип string в тип int
pubKey.E, err = strconv.Atoi(string(line)); checkErr(err)
// возвращаем ссылку на rsa.PublicKey
return &pubKey
} else {
// В этом случае дальнейшее действия программы не предусмотренною. По этому:
// Выводим что получили
fmt.Println("Error: unkown command ", string(line))
os.Exit(1) // Завершаем программу
}
return nil
}
Следующая функция является методом для ссылки на переменную типа
remoteConn
Проделывает ряд действий для зашифровки и отправки сообщения
func (rConn *remoteConn) sendCommand(comm string) {
// Зашифровываем сообщение
eComm, err := rsa.EncryptOAEP(sha1.New(), rand.Reader, rConn.pubK, []byte(comm), nil)
// sha1.New() вернёт данные типа hash.Hash
// С таким же успехм можно использовать sha512.New() sha256.New() ...
// rand.Reader тип которого io.Reader позволяет не задумываясь о платформе генерировать
// случайные числа из /dev/unrandom будь то Linux или CryptGenRandom API будь то Windows
// rConn.pubK - публичный ключ который мы получили в func waitPubKey
// []byte(comm) - конвертируем строку comm в срез байт ([]byte)
checkErr(err) // проверяем на ошибки
// Передаём зашифрованное сообщение по заранее установленному соединению
rConn.c.Write(eComm)
// rConn.c какого типа? - net.TCPConn у которого есть метод Write()
// http://golang.org/pkg/net/#TCPConn.Write
}
Ниже функция которая оперирует ранее объявленными функциями и в конечном итоге отправляет «клиенту» название сервера и приветствия на разных языках.
func listen() {
// Слушаем любой свободны порт
l, err := net.ListenTCP(tcpProtocol, listenAddr); checkErr(err)
// Выведем прослушиваемый порт
fmt.Println("Listen port: ", l.Addr().(*net.TCPAddr).Port)
// l == *net.TCPListener == ссылка на тип данных
// .Addr() http://golang.org/pkg/net/#TCPListener.Addr == метод для *net.TCPListener который возвращает "интерфейс"
// net.Addr http://golang.org/pkg/net/#Addr который в свою очередь содержит ссылку на TCPAddr - *net.TCPAddr
// и два метода Network() и String()
c, err := l.AcceptTCP(); checkErr(err)
// На этом этапе программа приостанавливает свою работу ожидая соединения по прослушиваемому порту
// AcceptTCP() - метод для *net.TCPListener http://golang.org/pkg/net/#TCPListener.AcceptTCP
//Возвращает установленное соединение и ошибку
fmt.Println("Connect from:", c.RemoteAddr())
// Вот 3 варианта которые подставив в fmt.Print[f|ln]() получим одинаковый результат
// 1. c.RemoteAddr()
// 2. c.RemoteAddr().(*net.TCPAddr)
// 3. c.RemoteAddr().String()
// В первый двух случаях функции: fmt.Println(), fmt.Print(), fmt.Printf() попытаются найти метод String()
// Иначе вывод будет таким как есть
// Таким образом мы получим соединение и ключ которым можно зашифровать это соединение
rConn := getRemoteConn(с)
// Шифруем и отправляем сообщения
rConn.sendCommand("Go Language Server v0.1 for learning")
rConn.sendCommand("Привет!")
rConn.sendCommand("Привіт!")
rConn.sendCommand("Прывітанне!")
rConn.sendCommand("Hello!")
rConn.sendCommand("Salut!")
rConn.sendCommand("ハイ!")
rConn.sendCommand("您好!")
rConn.sendCommand("안녕!")
rConn.sendCommand("Hej!")
}
На этом рассмотрение сервера окончено
func main() {
listen()
}
Клиент
package main
import(
"fmt"
"net"
"os"
"bufio"
"crypto/rsa"
"crypto/rand"
"crypto/sha1"
"strconv"
)
const(
tcpProtocol = "tcp4"
keySize = 1024
readWriterSize = keySize/8
)
func checkErr(err os.Error){
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
var connectAddr = &net.TCPAddr{IP: net.IPv4(192,168,0,2), Port: 0}
// Считываем с командной строки нужный нам порт и пытаемся соединится с сервером
func connectTo() *net.TCPConn{
// Выводим текст "Enter port:" без перехода но новую строку
fmt.Print("Enter port:")
// Считываем число с консоли в десятичном формате "%d"
fmt.Scanf("%d", &connectAddr.Port)
// Scanf не возвращает значение зато замечательно работает если передать туда ссылку
fmt.Println("Connect to", connectAddr)
// Создаём соединение с сервером
c ,err := net.DialTCP(tcpProtocol, nil, connectAddr); checkErr(err)
return c
}
// Функция в определённом порядке отправляет PublicKey
func sendKey(c *net.TCPConn, k *rsa.PrivateKey) {
// Говорим серверу что сейчас будет передан PublicKey
c.Write([]byte("CONNECT\n"))
// передаём N типа *big.Int
c.Write([]byte(k.PublicKey.N.String() + "\n"))
// String() конвертирует *big.Int в string
// передаём E типа int
c.Write([]byte(strconv.Itoa(k.PublicKey.E) + "\n"))
// strconv.Itoa() конвертирует int в string
// []byte() конвертирует "строку" в срез байт
}
// Читает и освобождает определённый кусок буфера
// Вернёт срез байт
func getBytes(buf *bufio.Reader, n int) []byte {
// Читаем n байт
bytes, err:= buf.Peek(n); checkErr(err)
// Освобождаем n байт
skipBytes(buf, n)
return bytes
}
// Освобождает, пропускает определённое количество байт
func skipBytes(buf *bufio.Reader, skipCount int){
for i:=0; i<skipCount; i++ {
buf.ReadByte()
}
}
func main() {
// Соединяемся с сервером
c := connectTo()
// Буферизирует всё что приходит от соединения "c"
buf := bufio.NewReader(с)
// Создаём приватный ключ в составе которого уже есть публичный ключ
k, err := rsa.GenerateKey(rand.Reader, keySize); checkErr(err)
// Отправляем серверу публичный ключ
sendKey(c, k)
// В цикле принимаем зашифрованные сообщения от сервера
for {
// Получаем зашифрованное сообщение в байтах
cryptMsg := getBytes(buf, readWriterSize)
// Расшифровываем сообщение
msg, err := rsa.DecryptOAEP(sha1.New(), rand.Reader, k, cryptMsg, nil)
// Проверяем на ошибку
checkErr(err)
// Выводим расшифрованное сообщение
fmt.Println(string(msg))
}
}
Исходники без единого комментария можно найти тут:
code.google.com/p/learning-go-language/source/browse