準備鑰匙(雙面):
[ -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 金鑰對對於各方來說都是靜態的(並且通常預先指定)。
此外,每個金鑰交換金鑰都由公鑰(與訊息一起傳遞)驗證。
使用預設設置,各方也會發送確認訊息以驗證是否已收到並感知訊息。並且(使用預設設定)各方等待來自遠端的確認訊息(另請參閱SessionOptions.AnswersMode
)。
如果此處一切順利,則*Session
將進入「協商」階段。但密鑰交換過程仍在後台定期執行。
在此階段,我們嘗試確定底層io.Writer
可以處理的資料包大小。因此,我們嘗試發送 3 種不同大小的資料包,看看其中哪一個能夠進行往返。然後以較短的時間間隔重複此過程。依此類推,最多4次。
可以透過SessionOptions.NegotiatorOptions.Enable
啟用或停用此行為。
當此過程完成時(或如果它已停用), *Session
將切換到「已建立」狀態。
這是*Session
正常運作的狀態,因此您可以透過它發送和接收訊息。在到達此階段之前嘗試發送的訊息將在*Session
到達此階段時立即發送。
Closing
是變成Closed
之前的過渡狀態。如果達到Closed
狀態,則表示會話已終止,並且不會再發生任何事情。
Async
進行同步寫入。notewakeup
而不是Cond.Wait