准备钥匙(双面):
[ -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
是一个自定义编号,用于识别哪个流(将发送方与远程端适当的接收方连接)。在上面的示例中,它使用0
作为channelID
值,但它可以是范围内的任何值: 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.86MiB/s 的速度,您需要异步发送大量消息(彼此之间),以便在通过后端连接发送/接收时将它们合并。
此外,这个 800MiB/s 更多的是关于本地主机的情况。更现实的网络情况(如果我们有 MTU ~= 1400)是:
BenchmarkSessionWriteMessageAsyncRead1300_max1400-8 117862 10277 ns/op 126.49 MB/s 267 B/op 10 allocs/op
远程端通过(密钥交换消息的)ED25519 签名进行身份验证。
密钥交换通过 ECDH 与 X25519 执行。如果设置了 PSK,则它是与常量盐值连接的 PSK,使用blake3.Sum256
和sha3.Sum256
进行哈希处理,并用于对(交换的)密钥进行异或。
所得值用作 XChaCha20 的加密密钥。该密钥在代码中称为cipherKey
。
密钥(通过 ECDH 接收)每分钟更新一次。因此cipherKey
也会每分钟更新一次。
SessionID
由第一个密钥交换消息进行交换。 SessionID
是 UnixNano(会话初始化时的)和随机 64 位整数的组合。
此外,每个数据包都以唯一的(对于会话而言)纯文本PacketID
开头(实际上,如果设置了 PSK,则使用作为加盐 PSK 的哈希值派生的密钥对PacketID
进行加密)。
PacketID
和SessionID
的组合用作 XChaCha20 的 IV/NONCE,并且cipherKey
用作密钥。
值得一提的是, PacketID
仅在会话中是唯一的。所以它只是从 0 开始,然后对于每一条下一条消息加 1。因此,如果系统时钟损坏,则SessionID
的 64 位随机值可以保证 NONCE 的唯一性。
消息验证是使用 Poly1305 完成的。 Poly1305 的密钥使用了 blake3.Sum256 哈希值:
PacketID
和cipherKey
通过常量值进行异或运算的串联。 PacketID
应该只会增加。接收到的 PacketID 会存储在有限的值窗口中。如果收到具有相同PacketID
(已经是)或具有较小PackerID
(小于窗口中的最小可能值)的数据包,则该数据包将被忽略。
使用默认设置的成功会话会经历以下状态/阶段/阶段:
这是解析所有选项并初始化所有必需的 goroutine 的阶段。
之后*Session
将切换到“密钥交换”状态。
在此阶段,双方(本地一方和远程一方)正在使用 ECDH 公钥交换以获得对称共享密钥(请参阅“安全设计”)。
此外,如果设置了远程身份,那么我们会验证它是否匹配,并忽略与任何其他 ED25519 公钥的任何密钥交换消息(不要与 ECDH 公钥混淆):ED25519 密钥对对于各方来说都是静态的(并且通常预先指定)。定义),而 ECDH 密钥对是为每次密钥交换生成的。
此外,每个密钥交换密钥都由公钥(与消息一起传递)验证。
使用默认设置,各方还会发送确认消息以验证是否已收到并感知消息。并且(使用默认设置)各方等待来自远程端的确认消息(另请参阅SessionOptions.AnswersMode
)。
如果此处一切顺利,则*Session
将进入“协商”阶段。但密钥交换过程仍然在后台定期执行。
在此阶段,我们尝试确定底层io.Writer
可以处理的数据包大小。因此,我们尝试发送 3 种不同大小的数据包,看看其中哪一个能够进行往返。然后以较短的时间间隔重复该过程。依此类推,最多4次。
可以通过SessionOptions.NegotiatorOptions.Enable
启用或禁用此行为。
当此过程完成时(或者如果它被禁用), *Session
将切换到“已建立”状态。
这是*Session
正常运行的状态,因此您可以通过它发送和接收消息。在到达此阶段之前尝试发送的消息将在*Session
到达此阶段时立即发送。
Closing
是变为Closed
之前的过渡状态。如果达到Closed
状态,则意味着会话已终止,并且不会再发生任何事情。
Async
进行同步写入。notewakeup
而不是Cond.Wait