Этот код написан с целью самообучения. Чтоб закрепить материал я решил немного прокомментировать проделанную работу.
Сразу скажу: на компилируемых языках не писал.
[к] — клиент
[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.TCPAddrАмперсанд "&" перед net.TCPAddr вернёт ссылку на этот тип.var listenAddr = &net.TCPAddr{IP: net.IPv4(192,168,0,4), Port: 0}
«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
