この記事は Go 2 Advent Calendar 2020 の23日目の記事です。
Sodiumとは
Sodium[1]は使いやすさを目的に開発された暗号学ライブラリです。 暗号の誤用に関する研究分野では、誤りの検出や復元のため技術とそれに伴う影響が議論されています。 一方で誤用そのものを防ぐような方法についての議論は比較的少数です[13]。 Sodiumは使いやすさからこの課題を解決するライブラリの一つです。
また使いやすさだけでなく、portable、cross-compilable、installableであることも謳われています。 awesome-cryptography[2]で検索してみた所、以下のように多様な言語で利用することができます。
C
- tonyg/js-nacl: Pure-Javascript High-level API to Emscripten-compiled libsodium routines.
- jedisct1/libsodium.js: libsodium compiled to Webassembly and pure JavaScript, with convenient wrappers.
- Halite - Simple PHP Cryptography Library - Paragon Initiative Enterprises
- scrothers/libsodium-laravel: Laravel integration for libsodium
Rust
Swift
GoでSodiumを使う
GoでもSodiumを扱うことができます。packageはnacl/ · pkg.go.devです。[1][14]にあるようにSodiumはNaClのforkで、Goはnacl packageとして準標準ライブラリに用意されています。
今回はnacl/boxが提供している、公開鍵を使ったメッセージの認証と暗号化を試してみようと思います。内容は[16]をベースとしています。
実装に移る前にSodiumの特徴や注意点をいくつか述べておきます。
まず、メッセージは暗号化されますが、メッセージの長さは秘匿されません。暗号文の長さから元の平文が推測できてしまうようなアプリケーションには向きません。
また、Sodiumではメッセージにnonceを付与します。メッセージごとに異なるnonceを用いることは呼び出し側の責任です。
その他の注意点としては、[16]にも書かれていますが、メッセージ全体を処理するためにメッセージをメモリ上に保持する必要があるため、小さなメッセージが推奨されています。なお、暗号化のオーバーヘッドも存在しますが、8KB程度のメッセージでは十分に償却されるとのことです。
では本題の実装に入っていきましょう。 今回はclientからserverを送信するメッセージの暗号化を行います。
まず、GenerateKey
を用いて、鍵ペアを生成します。
認証のためclient、serverの両方で鍵ペアを生成します。
// client clientPublicKey, clientPrivateKey, err := box.GenerateKey(crypto_rand.Reader) if err != nil { panic(err) } // server serverPublicKey, serverPrivateKey, err := box.GenerateKey(crypto_rand.Reader) if err != nil { panic(err) }
次にメッセージに付与するnonce
を生成します。nonce
の長さは24 bytesです。
nonce
の使い回しは厳禁です。メッセージごとに生成しましょう。
var nonce [24]byte if _, err := io.ReadFull(crypto_rand.Reader, nonce[:]); err != nil { panic(err) }
いよいよ暗号化です。暗号化にはSeal
を用います。
暗号化対象のメッセージをmsg := []byte("sodium msg")
とし、認証付きで公開鍵暗号を施します。
encrypted := box.Seal(nonce[:], msg, &nonce, serverPublicKey, clientPrivateKey)
clientの秘密鍵clientPrivateKey
だけでなく、serverの公開鍵serverPublicKey
を渡していることに注意してください。
復号にはサーバの秘密鍵が必要であるとともに、clientの公開鍵によってclientからのメッセージであることを認証します。
ちなみにnonce
は暗号文の先頭に付与されます。
筆者の環境で実行した結果は以下のようになりました。
encrypted
の先頭24 bytesがnonce
となっていることがわかります。
nonce: [176 240 87 14 190 92 224 60 245 16 119 163 71 18 1 177 57 118 139 46 141 4 99 117] encrypted: [176 240 87 14 190 92 224 60 245 16 119 163 71 18 1 177 57 118 139 46 141 4 99 117 65 153 138 25 206 247 18 42 0 162 85 88 223 68 203 63 241 28 35 232 242 176 184 56 97 177]
また上記のlen(encrypted)
は50 bytesです。
msg
(sodium msg
)が10 bytesなので、nonce
の24 bytesを差し引きいても16 bytesのオーバーヘッドがあります。このオーバーヘッドは認証のために付与されたもので、暗号文とはオーバーラップしない設計となっています。
では復号してみましょう。
実際のアプリケーションではclientからserverへencrypted
が送信されてからの復号となりますが、ここではserverで受信済みとして先に進みます。
Seal
されたメッセージの復号にはOpen
を用います。
Open
のシグネチャは以下のようになっています。
func Open(out, box []byte, nonce *[24]byte, peersPublicKey, privateKey *[32]byte) ([]byte, bool)
2つ目の引数に注目いただきたいのですが、nonce
が必要です。
encrypted
の先頭24 bytesがnonce
になっていたことを思い出し、encrypted
から取り出します。
var decryptNonce [24]byte copy(decryptNonce[:], encrypted[:24])
必要な引数がすべて揃ったので、serverの秘密鍵serverPrivateKey
とclientの公開鍵clientPublicKey
を用いてOpen
を実行します。
decrypted, _ := box.Open(nil, encrypted[24:], &decryptNonce, clientPublicKey, serverPrivateKey)
正しいserverの秘密鍵とclientの公開鍵があれば、認証と復号に成功し、元のmsg
と同一のdecrypted
を得ることができます。
まとめ
Sodiumの紹介と、Goで認証付き公開鍵暗号の暗号化/復号を試してみました。認証付きで公開鍵暗号を使えるので正しい相手から受信したことを保証したい場合などに有用でしょう。また利用できる言語が多いことも魅力的ですね。筆者はRustとjsのcompatibilityを手元でも動作確認しました。機会があれば、いつか記事にしたいと思います。Goとのcompatibilityも試してみたいですね。
また、同じようなプロジェクトは他にもMonocypher[17]、Themis[18]、Tink[19]もあるようです。特にTinkはGoogleのプロジェクトであることからも気になっています。APIのインターフェースが違うようなので、使い勝手といった視点も含めて触っておきたいところです。
References
- [1] Introduction - libsodium https://doc.libsodium.org/
- [2] sobolevn/awesome-cryptography: A curated list of cryptography resources and links. https://github.com/sobolevn/awesome-cryptography
- [3] jedisct1/libsodium: A modern, portable, easy to use crypto library. https://github.com/jedisct1/libsodium
- [4] adamcaudill/libsodium-net: libsodium for .NET - A secure cryptographic library https://github.com/adamcaudill/libsodium-net
- [5] bitbeans/StreamCryptor: Stream encryption & decryption with libsodium and protobuf https://github.com/bitbeans/StreamCryptor
- [6] tonyg/js-nacl: Pure-Javascript High-level API to Emscripten-compiled libsodium routines. https://github.com/tonyg/js-nacl
- [7] jedisct1/libsodium.js: libsodium compiled to Webassembly and pure JavaScript, with convenient wrappers. https://github.com/jedisct1/libsodium.js
- [8] Kalium: Java binding to the Networking and Cryptography (NaCl) library with the awesomeness of libsodium http://abstractj.github.io/kalium/
- [9] Halite - Simple PHP Cryptography Library - Paragon Initiative Enterprises https://paragonie.com/project/halite
- [10] scrothers/libsodium-laravel: Laravel integration for libsodium https://github.com/scrothers/libsodium-laravel
- [11] sodiumoxide/sodiumoxide: Sodium Oxide: Fast cryptographic library for Rust (bindings to libsodium) https://github.com/sodiumoxide/sodiumoxide
- [12] jedisct1/swift-sodium: Safe and easy to use crypto for iOS and macOS https://github.com/jedisct1/swift-sodium
- [13] Blochberger, Maximilian, Tom Petersen, and Hannes Federrath. "Mitigating Cryptographic Mistakes by Design." Mensch und Computer 2019-Workshopband (2019). https://svs.informatik.uni-hamburg.de/publications/2019/2019-09-05-crypto-api-design-muc2019.pdf
- [14] NaCl (ソフトウェア) - Wikipedia https://ja.wikipedia.org/wiki/NaCl_(%E3%82%BD%E3%83%95%E3%83%88%E3%82%A6%E3%82%A7%E3%82%A2)
- [15] nacl/ · pkg.go.dev https://pkg.go.dev/golang.org/x/crypto/nacl
- [16] box · pkg.go.dev https://pkg.go.dev/golang.org/x/crypto@v0.0.0-20201217014255-9d1352758620/nacl/box
- [17] Monocypher: Boring crypto that simply works, https://monocypher.org/
- [18] Themis: Cross-platform library for secure data storage, message exchange, socket connections, and authentication, https://www.cossacklabs.com/themis/
- [19] google/tink: Tink is a multi-language, cross-platform, open source library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse. https://github.com/google/tink