تحضير المفاتيح (على كلا الجانبين):
[ -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 .
يوجد أيضًا نوع خاص من الرسائل هو 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.86 ميجابايت/ثانية على الرسائل ذات البايت الواحد، فإنك تحتاج إلى إرسال الكثير منها بشكل غير متزامن (من بعضها البعض)، لذلك سيتم دمجها أثناء الإرسال/الاستقبال عبر اتصال الواجهة الخلفية.
كما أن سرعة 800 ميجابايت/ثانية هذه تتعلق أكثر بحالة المضيف المحلي. وحالة الشبكة الأكثر واقعية (إذا كان لدينا 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 أقل (من أقل قيمة ممكنة في النافذة)، فسيتم تجاهل الحزمة فقط.
تمر الجلسة الناجحة بالإعدادات الافتراضية عبر حالات/مراحل/مراحل:
هذه هي المرحلة التي يتم فيها تحليل جميع الخيارات وتهيئة كافة الإجراءات المطلوبة.
بعد ذلك سيتم تحويل *Session إلى حالة "تبادل المفاتيح".
في هذه المرحلة، يقوم الطرفان (المحلي والبعيد) بتبادل المفاتيح العامة لـ ECDH للحصول على مفتاح مشترك متماثل (انظر "التصميم الأمني").
أيضًا، إذا تم تعيين هوية عن بعد، فإننا نتحقق مما إذا كانت متطابقة، ونتجاهل أي رسائل تبادل مفاتيح مع أي مفاتيح عامة أخرى لـ ED25519 (لا تخلط بين المفتاح العام ECDH): تكون أزواج المفاتيح ED25519 ثابتة لكل طرف (وعادةً ما تكون مسبقة- المحددة)، في حين يتم إنشاء أزواج مفاتيح ECDH لكل تبادل مفاتيح.
كما يتم التحقق من كل مفتاح لتبادل المفاتيح بواسطة المفتاح العام (الذي يتم تمريره مع الرسالة).
باستخدام الإعدادات الافتراضية، يرسل كل طرف أيضًا رسائل شكر للتحقق من استلام الرسائل واستلامها. و(بالإعدادات الافتراضية) ينتظر كل طرف رسالة إقرار من الجانب البعيد (انظر أيضًا SessionOptions.AnswersMode ).
إذا نجح كل شيء هنا، فإن *Session تنتقل إلى مرحلة "التفاوض". لكن عملية تبادل المفاتيح لا تزال تتم بشكل دوري في الخلفية.
نحاول في هذه المرحلة تحديد حجم الحزم التي يمكن لـ io.Writer الأساسي التعامل معها. لذلك نحاول إرسال حزم بثلاثة أحجام مختلفة ونرى أي منها سيكون قادرًا على القيام برحلة ذهابًا وإيابًا. ثم كرر الإجراء في فترة زمنية أقصر. وهكذا حتى 4 مرات.
يمكن تمكين هذا السلوك أو تعطيله من خلال SessionOptions.NegotiatorOptions.Enable .
عند انتهاء هذا الإجراء (أو في حالة تعطيله)، يتم تحويل *Session إلى الحالة "تم التأسيس".
هذه هي الحالة التي تعمل فيها *Session بشكل طبيعي، لذا يمكنك إرسال واستقبال الرسائل من خلالها. والرسائل التي تتم محاولة إرسالها قبل الوصول إلى هذه المرحلة سيتم إرسالها بمجرد وصول *Session إلى هذه المرحلة.
Closing هو حالة انتقالية قبل أن يصبح Closed . إذا تم الوصول إلى حالة Closed فهذا يعني أن الجلسة قد ماتت ولن يحدث لها أي شيء على الإطلاق.
Async للكتابة المتزامنة.notewakeup بدلاً من Cond.Wait