キーを準備します (両側):
[ -f ~ /.ssh/id_ed25519 ] && [ -f ~ /.ssh/id_ed25519.pub ] || ssh-keygen -t ed25519
scp ~ /.ssh/id_ed25519.pub remote:from_remote_side/
暗号化されたio.ReadWriteCloser
、簡単:
// Generate (if not exists) and read ED25519 keys
identity , err := secureio . NewIdentity ( `/home/user/.ssh` )
// Read remote identity
remoteIdentity , err := secureio . NewRemoteIdentityFromPublicKey ( `/home/user/from_remote_side/id_ed25519.pub` )
// Create a connection
conn , err := net . Dial ( "udp" , "10.0.0.2:1234" )
// Create an encrypted connection (and exchange keys using ECDH and verify remote side by ED25519 signature).
session := identity . NewSession ( remoteIdentity , conn , nil , nil )
session . Start ( context . Background ())
// Use it!
// Write to it
_ , err = session . Write ( someData )
// Or/and read from it
_ , err = session . Read ( someData )
受信機をセットアップします。
session . SetHandlerFuncs ( secureio . MessageTypeChannel ( 0 ), func ( payload [] byte ) {
fmt . Println ( "I received a payload:" , payload )
}, func ( err error ) {
panic ( err )
})
メッセージを同期的に送信します。
_ , err := session . WriteMessage ( secureio . MessageTypeChannel ( 0 ), payload )
または
メッセージを非同期に送信します。
// Schedule the sending of the payload
sendInfo := session . WriteMessageAsync ( secureio . MessageTypeChannel ( 0 ), payload )
[.. your another stuff here if you want ..]
// Wait until the real sending
<- sendInfo . Done ()
// Here you get the error if any:
err := sendInfo. Err
// It's not necessary, but helps to reduce the pressure on GC (so to optimize CPU and RAM utilization)
sendInfo . Release ()
カスタム チャネルの MessageType は、関数MessageTypeChannel(channelID uint32)
を介して作成できます。 channelID
、どのフローであるかを識別するためのカスタム番号です (送信者をリモート側の適切な受信者に接続するため)。上の例では、 channelID
値として0
が使用されていますが、 0 <= x <= 2**31
範囲内の任意の値を使用できます。
また、特別な MessageType もあります。 MessageTypeReadWrite
、デフォルトのRead()
/ Write()
に使用されます。ただし、このフローをカスタム ハンドラーにリダイレクトすることもできます。
SessionOptions.MaxPayloadSize
を調整する必要があります。SessionOptions.SendDelay
を&[]time.Duration{0}[0]
に設定できます。 ベンチマークは、UNIX ソケットを介した通信で実行されました。
BenchmarkSessionWriteRead1-8 10000 118153 ns/op 0.01 MB/s 468 B/op 8 allocs/op
BenchmarkSessionWriteRead16-8 10000 118019 ns/op 0.14 MB/s 455 B/op 8 allocs/op
BenchmarkSessionWriteRead1024-8 9710 119238 ns/op 8.59 MB/s 441 B/op 8 allocs/op
BenchmarkSessionWriteRead32000-8 6980 173441 ns/op 184.50 MB/s 488 B/op 9 allocs/op
BenchmarkSessionWriteRead64000-8 3994 310038 ns/op 206.43 MB/s 629 B/op 9 allocs/op
BenchmarkSessionWriteMessageAsyncRead1-8 2285032 539 ns/op 1.86 MB/s 0 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead16-8 2109264 572 ns/op 27.99 MB/s 2 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead1024-8 480385 2404 ns/op 425.87 MB/s 15 B/op 0 allocs/op
BenchmarkSessionWriteMessageAsyncRead32000-8 30163 39131 ns/op 817.76 MB/s 162 B/op 5 allocs/op
BenchmarkSessionWriteMessageAsyncRead64000-8 15435 77898 ns/op 821.59 MB/s 317 B/op 10 allocs/op
このパッケージは非同期になるように設計されているため、基本的にWrite
WriteMessageAsync
のコードを囲む愚かなラッパーです。スループットを向上させるために、50 マイクロ秒以内に収集されたすべてのメッセージを 1 つにマージし、送信してから分割して戻します。これにより、システムコールやその他のオーバーヘッドの量を削減できます。そのため、1 バイト メッセージで 1.86MiB/s のような速度を達成するには、バックエンド接続を介して送受信している間にメッセージがマージされるように、大量のメッセージを (相互に) 非同期に送信する必要があります。
また、この 800MiB/s はローカルホストの場合に当てはまります。そして、より現実的なネットワークのケース (MTU ~= 1400 の場合) は次のとおりです。
BenchmarkSessionWriteMessageAsyncRead1300_max1400-8 117862 10277 ns/op 126.49 MB/s 267 B/op 10 allocs/op
リモート側は、(鍵交換メッセージの) ED25519 署名によって認証されます。
鍵交換は X25519 を使用して ECDH 経由で実行されます。 PSK が設定されている場合、PSK は定数ソルト値と連結され、 blake3.Sum256
およびsha3.Sum256
でハッシュされ、(交換された) キーの XOR に使用されます。
結果の値は、XChaCha20 の暗号化キーとして使用されます。このキーは、コード内ではcipherKey
と呼ばれます。
キー (ECDH 経由で受信) は 1 分ごとに更新されます。したがって、 cipherKey
も毎分更新されます。
SessionID
、最初の鍵交換メッセージによって交換されます。 SessionID
、UnixNano (セッションが初期化されたときの) とランダムな 64 ビット整数の組み合わせです。
また、各パケットは、(セッションに対して)一意の平文PacketID
で始まります(実際には、PSK が設定されている場合、 PacketID
ソルト付き PSK のハッシュとして導出されたキーで暗号化されます)。
PacketID
とSessionID
の組み合わせが XChaCha20 の IV/NONCE として使用され、 cipherKey
がキーとして使用されます。
PacketID
はセッション内でのみ一意であることを意図していることに注意してください。したがって、ゼロから始まり、次のメッセージごとに 1 ずつ増加します。したがって、システム クロックが壊れた場合、 NONCE の一意性はSessionID
の 64 ビットのランダムな値によって保証されます。
メッセージ認証は Poly1305 を使用して行われます。 Poly1305 のキーとして、次の blake3.Sum256 ハッシュが使用されました。
PacketID
とcipherKey
を定数値で XOR 演算した連結。 PacketID
増加するだけのはずです。受信した PacketID は、限られた値の範囲内で記憶されます。同じPacketID
(すでに存在していたもの) またはそれより小さいPackerID
(ウィンドウ内の最小値よりも小さい) を持つパケットを受信した場合、そのパケットは無視されます。
デフォルト設定で成功したセッションは、次の状態/フェーズ/ステージを経ます。
これは、すべてのオプションが解析され、必要なすべてのゴルーチンが初期化される段階です。
その後、 *Session
「鍵交換中」状態に切り替わります。
この段階では、両方の当事者 (ローカル側とリモート側) が ECDH 公開鍵を交換して対称共有鍵を取得します (「セキュリティ設計」を参照)。
また、リモート ID が設定されている場合は、それが一致するかどうかを確認し、他の ED25519 公開鍵との鍵交換メッセージを無視します (ECDH 公開鍵と混同しないでください): ED25519 鍵ペアは各当事者に対して静的です (通常は事前に定義されています)、ECDH キーペアはキー交換ごとに生成されます。
また、各鍵交換鍵は公開鍵 (メッセージとともに渡される) によって検証されます。
デフォルト設定では、各当事者はメッセージが受信され認識されたかどうかを確認するために確認メッセージも送信します。そして (デフォルト設定では) 各当事者はリモート側からの確認メッセージを待ちます ( SessionOptions.AnswersMode
も参照)。
ここですべてが成功すると、 *Session
「交渉」フェーズに進みます。ただし、キー交換プロセスは引き続きバックグラウンドで定期的に実行されます。
この段階では、基礎となるio.Writer
どのサイズのパケットを処理できるかを判断しようとします。そこで、3 つの異なるサイズのパケットを送信して、どれが往復できるかを調べます。その後、より短い間隔でこの手順を繰り返します。など、最大 4 回まで続きます。
この動作はSessionOptions.NegotiatorOptions.Enable
によって有効または無効にすることができます。
この手順が完了すると (または無効になっていると)、 *Session
「確立済み」状態に切り替わります。
これは*Session
が正常に動作している状態なので、*Session を通じてメッセージを送受信できます。また、この段階に到達する前に送信しようとしたメッセージは、 *Session
この段階に到達するとすぐに送信されます。
Closing
Closed
なる前の過渡的な状態です。 Closed
状態に達すると、セッションが終了し、セッションには何も起こらないことを意味します。
Async
使用しないでください。Cond.Wait
の代わりにnotewakeup
検討してください