키 준비(양쪽):
[ -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
범위의 모든 값이 될 수 있습니다.
또한 기본 Read()
/ Write()
에 사용되는 특수 MessageType MessageTypeReadWrite
있습니다. 하지만 이 흐름을 사용자 정의 핸들러로 리디렉션할 수 있습니다.
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는 localhost 사례에 관한 것입니다. 보다 현실적인 네트워크 사례(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가 설정된 경우 솔트 처리된 PSK의 해시로 파생된 키로 암호화된 PacketID
)로 시작됩니다.
XChaCha20에서는 PacketID
와 SessionID
의 조합이 IV/NONCE로 사용되며 cipherKey
키로 사용됩니다.
PacketID
세션 내에서만 고유해야 한다는 점을 언급할 가치가 있습니다. 따라서 0으로 시작하고 다음 메시지마다 1씩 증가합니다. 따라서 시스템 시계가 고장난 경우 SessionID
의 64비트 임의 값으로 NONCE의 고유성이 보장됩니다.
메시지 인증은 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
이 단계에 도달하자마자 전송됩니다.
Closing
Closed
되기 전의 과도기 상태입니다. Closed
상태에 도달하면 세션이 종료되어 더 이상 아무 일도 일어나지 않는다는 의미입니다.
Async
사용하지 마세요.Cond.Wait
대신 notewakeup
고려하세요.