เตรียมกุญแจ(ทั้งสองด้าน):
[ -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.MaxPayloadSizeSessionOptions.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 จึงเป็น wrapper โง่ ๆ รอบ ๆ โค้ดของ WriteMessageAsync เพื่อให้ได้รับปริมาณงานมากขึ้น ระบบจะรวมข้อความทั้งหมดของคุณที่รวบรวมในเวลา 50 ไมโครวินาทีเป็นข้อความเดียว จากนั้นส่ง แล้วแยกกลับ ช่วยลดจำนวนการโทรระบบและค่าใช้จ่ายอื่นๆ ดังนั้นเพื่อให้ได้ความเร็วประมาณ 1.86MiB/s สำหรับข้อความขนาด 1 ไบต์ คุณจะต้องส่งข้อความจำนวนมากแบบอะซิงโครนัส (จากกัน) ดังนั้นข้อความเหล่านั้นจะถูกรวมเข้าด้วยกันในขณะที่ส่ง/รับผ่านการเชื่อมต่อแบ็กเอนด์
นอกจากนี้ 800MiB/s นี้ยังเกี่ยวกับ localhost-case มากกว่าอีกด้วย และกรณีเครือข่ายที่สมจริงยิ่งขึ้น (ถ้าเรามี 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 และใช้กับ XOR คีย์ (แลกเปลี่ยน)
ค่าที่ได้จะถูกใช้เป็นคีย์เข้ารหัสสำหรับ XChaCha20 คีย์นี้เรียกว่า cipherKey ภายในโค้ด
คีย์ (รับผ่าน ECDH) จะได้รับการอัปเดตทุกนาที ดังนั้น cipherKey จะได้รับการอัปเดตทุกนาทีเช่นกัน
SessionID มีการแลกเปลี่ยนโดยข้อความแลกเปลี่ยนคีย์แรกสุด SessionID คือการรวมกันของ UnixNano (ของเวลาที่เริ่มต้นเซสชัน) และจำนวนเต็ม 64 บิตแบบสุ่ม
นอกจากนี้ แต่ละแพ็กเก็ตจะเริ่มต้นด้วย PacketID ข้อความธรรมดาที่ไม่ซ้ำกัน (สำหรับเซสชัน) (จริงๆ แล้วหากตั้งค่า PSK แล้ว PacketID จะถูกเข้ารหัสด้วยคีย์ที่ได้รับเป็นแฮชของ PSK แบบเค็ม)
การรวมกันของ PacketID และ SessionID ถูกใช้เป็น IV/NONCE สำหรับ XChaCha20 และ cipherKey ถูกใช้เป็นคีย์
น่ากล่าวถึงว่า PacketID มีวัตถุประสงค์เพื่อให้ไม่ซ้ำกันภายในเซสชันเท่านั้น ดังนั้นมันจึงเริ่มต้นด้วยศูนย์แล้วเพิ่มขึ้น 1 สำหรับแต่ละข้อความถัดไป ดังนั้นหากนาฬิกาของระบบเสีย ความเป็นเอกลักษณ์ของ NONCE จะถูกรับประกันด้วยค่าสุ่ม 64 บิตของ SessionID
การตรวจสอบข้อความทำได้โดยใช้ Poly1305 เป็นกุญแจสำคัญสำหรับ Poly1305 ที่ใช้แฮช blake3.Sum256 ของ:
PacketID และ cipherKey XOR-ed เข้าด้วยกันด้วยค่าคงที่ PacketID ควรจะเพิ่มขึ้นเท่านั้น PacketID ที่ได้รับจะถูกจดจำในหน้าต่างค่าที่จำกัด หากได้รับแพ็กเก็ตที่มี PacketID เดียวกัน (เหมือนเดิม) หรือมี PackerID น้อยกว่า (มากกว่าค่าต่ำสุดที่เป็นไปได้ในหน้าต่าง) แพ็กเก็ตนั้นจะถูกละเว้น
เซสชันที่ประสบความสำเร็จด้วยการตั้งค่าเริ่มต้นจะต้องผ่านสถานะ/ระยะ/ระยะ:
นี่คือขั้นตอนที่ตัวเลือกทั้งหมดจะถูกแยกวิเคราะห์และ goroutines ที่จำเป็นทั้งหมดกำลังถูกเตรียมใช้งาน
หลังจากนั้น *Session จะเปลี่ยนเป็นสถานะ "การแลกเปลี่ยนคีย์"
ในขั้นตอนนี้ ทั้งสองฝ่าย (ทั้งในพื้นที่และระยะไกล) กำลังแลกเปลี่ยนกับกุญแจสาธารณะ ECDH เพื่อรับกุญแจที่ใช้ร่วมกันแบบสมมาตร (ดู "การออกแบบความปลอดภัย")
นอกจากนี้ หากมีการตั้งค่าข้อมูลระบุตัวตนระยะไกล เราจะตรวจสอบว่าตรงกันหรือไม่ และไม่สนใจข้อความแลกเปลี่ยนคีย์ใดๆ กับคีย์สาธารณะ ED25519 อื่นๆ (อย่าสับสนกับคีย์สาธารณะ ECDH): คู่คีย์ ED25519 เป็นแบบคงที่สำหรับแต่ละฝ่าย (และโดยปกติจะก่อน- กำหนดไว้) ในขณะที่คู่คีย์ ECDH จะถูกสร้างขึ้นสำหรับการแลกเปลี่ยนคีย์แต่ละรายการ
นอกจากนี้ คีย์การแลกเปลี่ยนคีย์แต่ละคีย์ยังได้รับการตรวจสอบโดยคีย์สาธารณะ (ส่งผ่านพร้อมกับข้อความ)
ด้วยการตั้งค่าเริ่มต้น แต่ละฝ่ายยังส่งข้อความตอบรับเพื่อตรวจสอบว่าข้อความได้รับและรับรู้หรือไม่ และ (ด้วยการตั้งค่าเริ่มต้น) แต่ละฝ่ายจะรอข้อความตอบรับจากระยะไกล (ดู SessionOptions.AnswersMode เพิ่มเติม )
หากทุกอย่างสำเร็จที่นี่ *Session จะเข้าสู่ช่วง "การเจรจา" แต่กระบวนการแลกเปลี่ยนคีย์ยังคงดำเนินการอยู่เบื้องหลังเป็นระยะๆ
ในขั้นตอนนี้ เราพยายามกำหนดขนาดของแพ็กเก็ตที่ io.Writer พื้นฐานสามารถจัดการได้ เราเลยลองส่งซองมา 3 ขนาดด้วยกันดูว่าอันไหนจะสามารถไป-กลับได้ จากนั้นทำซ้ำขั้นตอนในช่วงเวลาที่สั้นลง และต่อเนื่องกันถึง 4 ครั้ง
ลักษณะการทำงานนี้สามารถเปิดหรือปิดใช้งานได้ผ่าน SessionOptions.NegotiatorOptions.Enable
เมื่อขั้นตอนนี้จะเสร็จสิ้น (หรือหากปิดใช้งานอยู่) *Session จะเปลี่ยนเป็นสถานะ "Established"
นี่คือสถานะที่ *Session ทำงานตามปกติ ดังนั้นคุณจึงสามารถส่งและรับข้อความผ่านเซสชันได้ และข้อความที่พยายามส่งก่อนที่จะถึงขั้นตอนนี้จะถูกส่งทันทีที่ *Session จะถึงขั้นตอนนี้
Closing เป็นสถานะเปลี่ยนผ่านก่อนที่จะกลายเป็น Closed หากถึงสถานะ Closed หมายความว่าเซสชั่นนั้นตายและจะไม่มีอะไรเกิดขึ้นอีกต่อไป
Async สำหรับการเขียนซิงค์notewakeup แทน Cond.Wait