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

KeyExchangeMLKem768X25519Sha256

using Org.BouncyCastle.Crypto; 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 : KeyExchangeECCurve25519 { private MLKemDecapsulator _mlkemDecapsulator; public override string Name => "mlkem768x25519-sha256"; protected override int HashSize => 256; protected override void StartImpl() { 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()); byte[] encoded = val2.get_Public().GetEncoded(); byte[] second = _impl.GenerateClientPublicKey(); _clientExchangeValue = encoded.Concat(second); SendMessage(new KeyExchangeHybridInitMessage(_clientExchangeValue)); } protected override void FinishImpl() { 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() + X25519PublicKeyParameters.KeySize) throw new SshConnectionException(string.Format(CultureInfo.CurrentCulture, "Bad S_Reply length: {0}.", serverExchangeValue.Length), DisconnectReason.KeyExchangeFailed); byte[] array = new byte[_mlkemDecapsulator.get_SecretLength()]; _mlkemDecapsulator.Decapsulate(serverExchangeValue, 0, _mlkemDecapsulator.get_EncapsulationLength(), array, 0, _mlkemDecapsulator.get_SecretLength()); byte[] second = _impl.CalculateAgreement(serverExchangeValue.Take(_mlkemDecapsulator.get_EncapsulationLength(), X25519PublicKeyParameters.KeySize)); base.SharedKey = CryptoAbstraction.HashSHA256(array.Concat(second)); } } }