【Golang】公開鍵と共通鍵で暗号化する【rsa, aes, pem, x509, cipher】

前回のプログラムを改良してサーバ/クライアントが送信するメッセージを暗号化してみました。


【Golang】jsonの内容をデコードする - くどはむと猫の窓

 

前回までに作ったプログラムはクライアントが送ったメッセージに" too!"を付けてサーバが送り返すプログラムでした。この通信はTCP通信なので経路上を流れるメッセージは簡単に除くことができます。

試しにクライアントから「hoge」というメッセージを送り、WireSharkを使ってサーバ側が開いているポート番号でやりとりされる内容を覗いてみます。

f:id:kudohamu:20141108030220j:plain

 

f:id:kudohamu:20141108030337p:plain

2つの画像の右下にクライアントから送られた「hoge」とサーバから返された「hoge too!」の文字があります。

このようにTCP通信でやりとりされるメッセージは外部から簡単に見ることが可能です。

そこで今回はこのメッセージを暗号化し、第3者からは簡単に見ることができないようにしてみました。

 

TLS/SSLなどでもお馴染みのハイブリッド暗号方式です。

具体的な手順としては

  1. (開始)
  2. クライアントから接続されたらサーバが公開鍵証明書を送る
  3. クライアントは送られてきた証明書が正しいことを確認する
  4. クライアントがセッション鍵(共通鍵)を生成し、それを公開鍵で暗号化してサーバに送る
  5. サーバは送られてきた「暗号化されたセッション鍵」を秘密鍵で復号化して取り出す
  6. クライアントはメッセージを共通鍵で暗号化し、サーバに送る
  7. サーバは送られてきた「暗号化されたメッセージ」を共通鍵を使って復号化してメッセージを取り出す
  8. サーバは送られてきたメッセージに「 too!」を付けたメッセージを作り、共通鍵で暗号化してクライアントに送る
  9. クライアントは送られてきた「暗号化されたメッセージ」を共通鍵を使って復号化してメッセージを取り出し、標準出力する
  10. (終了)

当然、共通鍵は1回のセッション(開始〜終了)ごとに新しいものを生成します。

 

少し長いですがサーバ側・クライアント側のコードを全部載せます。

サーバ側

 

クライアント側

 

これをWireSharkで見てみると

f:id:kudohamu:20141117134855j:plain

 

f:id:kudohamu:20141117134920j:plain

このようにちゃんと生の文章を見ることができなくなっています。

シェル上ではこのように表示されています。

f:id:kudohamu:20141117165852j:plain

f:id:kudohamu:20141117165837j:plain

 ちゃんと平文で見れてます。

f:id:kudohamu:20141117141507g:plain

 

 

やっていることは上で説明した通りです。

共通鍵にはAES、暗号モードはCTRを使っています。大体はgolang.orgの例を参考にやっています。

 

ブロック暗号モード?初期化ベクトル?という方にはcryptoパッケージ群は少々読み解きにくいかもしれません(僕もそうでした)。

そんな方は下の2つのサイトを読むだけでもパッケージの読みやすさがかなり変わると思います(特に1つ目のサイトは必見です)。

 

私は貧乏学生なのでちゃんとした証明書を発行できるはずもなく、いわゆるオレオレ証明書を使っているので

options := x509.VerifyOptions{
DNSName: config.Server.DNSName,
Roots: roots,
}
 
if _, err := crt.Verify(options); err != nil {
panic("failed to verify certificate: " + err.Error())
}

とDNSNameだけで証明書を検証していますがちゃんとした証明書を使う場合はVerifyOptionsにもっと指定したほうが良いかも。

 

encryptWriteは平文を共通鍵で暗号化してTCPConnにWriteする関数、decryptReadはTCPConnからReadした暗号文を共通鍵で復号して平文を返す関数です。本当は両方ともTCPConnをレシーバにしたかったのですが自分で定義した型でないとレシーバにできないようですね。悲しいです。

Connから連続でReadしたいときなんかもっとうまいやり方がありそうな気がしますが見つからないので一回応答を返してからReadしています。

何か良い方法があれば是非教えて下さい。

それとgolang.orgの例では暗号文の先頭バイトにNonce(初期化ベクトル)を入れていますが、これって「暗号文の先頭バイトにNonceが入っているぞ」ということが知られたらあまり良くない気がしますがどうなんでしょう?

 これもご存知の方、例よりもっと良い方法をお知りの方がいれば教えいただけると嬉しいです。

 

と、crypto以下の色々なパッケージを使ってtls/sslっぽい通信を実装しましたが実はこんなに面倒なことをしなくてもtlsパッケージがあります。大体のことをラップしてくれています。しかし、他の言語にも言えることですがGo言語は特にどれだけ多くのパッケージを知っているかが重要だと思っています。ググラビリティも低いですし。

なので今回はあえてtlsパッケージを使わずに実装してみました。

 

次の目標はDBを使うような改良、あと変数名が長くて真のGolanger(?)Gopherに怒られそうなのでもっと短くします(messageとか)。余力と時間があればtlsパッケージを使った形も書いてみたいです。

 

ʕ◔ϖ◔ʔ

【Golang】jsonの内容をデコードする

前回のプログラムを改良してserverのIPとポート番号をハードコーディングではなくjsonファイルから取得できるようにしました。


【Golang】Socket通信するプログラムを書いてみた - くどはむと猫の窓

 

今回参考にしたサイトは以下です。

 

 

 

前回まではgolang.jpの方をまず最初に参考にしていたのですが、よく読んでいるとgolang.jpのosパッケージのOpen関数シグネチャgolang.orgのと違っていたりと、ちょっと信頼できるか怪しい?のでgolang.orgの方をまず最初に参考にするように変えました。

それにしてもこれは情報が古いのか、OpenFile関数と間違えているのか、どうなんでしょう。

 

話を戻します。

jsonパッケージのtype Decoderのところにコード例が載っています。

その中で使われている関数でまず目につくのはこれ

func NewDecoder(r io.Reader) *Decoder

NewDecoder関数はioパッケージのReaderインターフェースを実装している型の変数を引数に取り、Decoder型(のポインタ変数)を返すようです。

 

その次に目につくのはこれ

func (dec *Decoder) Decode(v interface{}) error

さっき返ってきたDecoderをレシーバとするDecode関数で、引数は空インターフェースです。つまりGoの型ならなんでも引数にしていいというわけですね。何それ。

「Unmrshalのところも読めよ」と書いてあるので読んでみると、jsonの型は以下の6つのどれかになるはずだから、この中でベストな型にデコードする的なことが書かれています。

bool, for JSON booleans
float64, for JSON numbers
string, for JSON strings
[]interface{}, for JSON arrays
map[string]interface{}, for JSON objects
nil for JSON null

またUnmarshalについてはJSON and Goにも書いてあり、ここでやっと「ああ、これと同じようなことが起きているのか」と分かりました。

type Decoderのところの例ではjsonの構造に合わせた構造体を定義し、その型の変数(ポインタ)を引数として渡していますが、これは引数として渡された構造体の変数名などの情報をもとに、型アサーションした結果が入れられているという感じでしょう。

JSON and Go下の方に例が載っていますが、構造体を用意しなくても自分で型アサーションを使って値を振り分ける関数を書けば同じようなことができるようです。interface便利。

とはいえ面倒なので例の通り構造体を使うことにしました。

 

ところで、NewDecoderの引数となっているReaderインターフェースはどのようなものでしょうか?そのままリンク先に飛ぶと

type Reader interface {
        Read(p []byte) (n int, err error)
}

となっています。

このRead関数を実装している型ならよいのですね。

僕はjsonデータをjsonファイルから読みたいと思っていますが、NewDecoderに渡せるでしょうか?まずは読みたいファイルを開く関数を探します。

osパッケージのOpen関数がそれっぽいです。

func Open(name string) (file *File, err error)

この関数は読み込んだファイルのファイルディスクリプタをFile型の変数として返します。

このFile型については同じosパッケージ内に書かれています。

そのまま読み進めていくと...

func (f *File) Read(b []byte) (n int, err error)

ありました。ReaderインターフェースのRead関数と同じシグネチャです。

ということはFile型はReaderインターフェースを実装しているのでこれをNewDecoderに渡すことができそうですね。

 

まとめると以下のような順序になります。

  1. jsonと同じ構造の構造体を定義する
  2. Open関数でjsonファイルをオープン
  3. Open関数で得たファイルディスクリプタをNewDecoderに渡す
  4. NewDecoderのDecode関数を呼び出す(引数は構造体の変数)

 

ということでここまでの情報を元に改良したプログラムが以下になります。

サーバ側

サーバ側のjsonはシンプルです。

{

  "Port": "55555"
}

 

 

クライアント側

クライアント側のjsonはこんな感じです。

{

  "server": {
    "IP": "192.xxx.xxx.xxx",
    "Port": "55556"
  }
}

 

これでjsonから設定情報を読み込むということができるようになりました。

他にもjsonに追加したくなったら構造体を修正してやればいいだけなので簡単ですね。

 

前回のプログラムではクライアントは自分のIPやポート番号も入力しなければなりませんでしたが、 DialTCP関数の第2引数をnilにすれば自動でローカルのIPと(空いてる)ポートを取ってくれるようなのでnilに変更しました。

 

次の目標は、今サーバ用もクライアント用もコマンドを打ったディレクトリからjsonファイルを探すのでこれをどこから呼び出しても特定のディレクトリから探すようにすること、そして通信を暗号化して送るように改良したいなと思っています。

 

【Golang】Socket通信するプログラムを書いてみた

最近Go言語を勉強しています。リッチな言語とはいえないですがその分軽いですし、何より楽しいです。

 

golang.jpA Tour of Goをひと通り読み終えたのでまずはサーバとクライアントでSocket通信(のうちのTCP通信)を行うプログラムを書いてみました。

これを書くにあたって以下のページが非常に参考になりました。実際かなりお手本にさせてもらっています。

 

 

勉強、コーディングの手順としては

  1. golang.jpの必要なパッケージドキュメントを読む。
  2. 1を読んでどのようにコードを組んでいけば良いか分からなかったら参考になりそうなものを探す
  3. 参考にしているサイトで分からない関数が出てきたらgolang.jpに戻ってそこを読む
  4. golang.jpに載っていない関数などは本家を探す

という感じです。

 

以下がコードです。

55555ポートを監視し続け、クライアントから接続されたらゴルーチンを作成しその中でR/Wしています。

接続ごとにR/Wの処理部分をゴルーチンとして切りだすのでクライアントからの複数の接続を捌けます。

 

ここを書いているときに「スライス便利だな」と思いました。

messageBuf := make([]byte, 1024)
messageLen, err := conn.Read(messageBuf)
checkError(err)
 
message := string(messageBuf[:messageLen])

 

Read関数はJavaなどと同じような使い方です。引数のbyte配列に読み込んだバイト列が入ります。これをstring型にキャストして保持するのですが、読み込んだバイト列は予め確保しておいた配列の長さより短い可能性が高いです。

そのため

messageBuf[:messageLen]

の部分で実際に読み込んだ長さまでスライスしたものをキャストしてます。

 

 

次がクライアント側です。

GoでSocket通信プログラム(クライアント側)

 

一部参考にできそうなところがなく、netパッケージを何度も読みながら書いたので不格好なコードかもしれません。

とはいえこれで立派にSocket通信ができます。

 

両方を見てもらえば分かる通りクライアントから送られたメッセージに"too!"をつけて返すだけの簡単なものですがなかなか勉強になりました。

 

ここからどんどん改良して少しづつ複雑なものを作っていきたいです。

今クライアント側でサーバのIPやポートをハードコーディングしているのでこれを外側から変更できるようにするのがひとまずの目標です。

これを引数で指定できるようにするのがプログラミング初学習のセオリーっぽいですがさすがに面白く無いのでjsonからパースして読み込めるようにでもしたいですね。