<PackageReference Include="SSH.NET" Version="2025.0.0" />

KeyExchangeMLKem768X25519Sha256

using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Agreement; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Kems; using Org.BouncyCastle.Crypto.Parameters; using Renci.SshNet.Abstractions; using Renci.SshNet.Common; using Renci.SshNet.Messages.Transport; using System.Globalization; namespace Renci.SshNet.Security { internal sealed class KeyExchangeMLKem768X25519Sha256 : KeyExchangeEC { private MLKemDecapsulator _mlkemDecapsulator; private X25519Agreement _x25519Agreement; public override string Name => "mlkem768x25519-sha256"; protected override int HashSize => 256; public override void Start(Session session, KeyExchangeInitMessage message, bool sendClientInitMessage) { base.Start(session, message, sendClientInitMessage); base.Session.RegisterMessage("SSH_MSG_KEX_HYBRID_REPLY"); base.Session.KeyExchangeHybridReplyMessageReceived += Session_KeyExchangeHybridReplyMessageReceived; MLKemKeyPairGenerator val = new MLKemKeyPairGenerator(); val.Init(new MLKemKeyGenerationParameters(CryptoAbstraction.SecureRandom, MLKemParameters.ml_kem_768)); AsymmetricCipherKeyPair val2 = val.GenerateKeyPair(); _mlkemDecapsulator = new MLKemDecapsulator(MLKemParameters.ml_kem_768); _mlkemDecapsulator.Init(val2.get_Private()); X25519KeyPairGenerator val3 = new X25519KeyPairGenerator(); val3.Init(new X25519KeyGenerationParameters(CryptoAbstraction.SecureRandom)); AsymmetricCipherKeyPair val4 = val3.GenerateKeyPair(); _x25519Agreement = new X25519Agreement(); _x25519Agreement.Init(val4.get_Private()); byte[] encoded = val2.get_Public().GetEncoded(); byte[] encoded2 = val4.get_Public().GetEncoded(); _clientExchangeValue = encoded.Concat(encoded2); SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue)); } public override void Finish() { base.Finish(); base.Session.KeyExchangeHybridReplyMessageReceived -= Session_KeyExchangeHybridReplyMessageReceived; } protected override byte[] Hash(byte[] hashData) { return CryptoAbstraction.HashSHA256(hashData); } private void Session_KeyExchangeHybridReplyMessageReceived(object sender, MessageEventArgs<KeyExchangeHybridReplyMessage> e) { KeyExchangeHybridReplyMessage message = e.Message; base.Session.UnRegisterMessage("SSH_MSG_KEX_HYBRID_REPLY"); HandleServerHybridReply(message.KS, message.SReply, message.Signature); Finish(); } private void HandleServerHybridReply(byte[] hostKey, byte[] serverExchangeValue, byte[] signature) { _serverExchangeValue = serverExchangeValue; _hostKey = hostKey; _signature = signature; if (serverExchangeValue.Length != _mlkemDecapsulator.get_EncapsulationLength() + _x25519Agreement.get_AgreementSize()) throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Bad S_Reply length: {0}.", serverExchangeValue.Length), DisconnectReason.KeyExchangeFailed); byte[] array = new byte[_mlkemDecapsulator.get_SecretLength() + _x25519Agreement.get_AgreementSize()]; _mlkemDecapsulator.Decapsulate(serverExchangeValue, 0, _mlkemDecapsulator.get_EncapsulationLength(), array, 0, _mlkemDecapsulator.get_SecretLength()); X25519PublicKeyParameters val = new X25519PublicKeyParameters(serverExchangeValue, _mlkemDecapsulator.get_EncapsulationLength()); _x25519Agreement.CalculateAgreement(val, array, _mlkemDecapsulator.get_SecretLength()); base.SharedKey = CryptoAbstraction.HashSHA256(array); } } }