Skip to content

Commit 6996711

Browse files
kpcyrdEugeny
andauthored
Replace libcrux-ml-kem with RustCrypto ml-kem (#660)
Co-authored-by: Eugene <inbox@null.page>
1 parent 829d385 commit 6996711

2 files changed

Lines changed: 31 additions & 32 deletions

File tree

russh/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ getrandom = { version = "0.2.15", features = ["js"] }
5454
hex-literal = "0.4"
5555
hmac.workspace = true
5656
inout = { version = "0.1", features = ["std"] }
57-
libcrux-ml-kem = { version = "0.0.4" }
5857
log.workspace = true
5958
md5 = "0.7"
59+
ml-kem = "0.2.3"
6060
num-bigint = { version = "0.4.2", features = ["rand"] }
6161
# num-integer = "0.1"
6262
p256 = { version = "0.13", features = ["ecdh"] }
@@ -90,7 +90,7 @@ yasna = { version = "0.5.0", features = [
9090
"num-bigint",
9191
], optional = true }
9292
zeroize = "1.7"
93-
criterion = { version = "0.3", optional = true, features = ["html_reports"] }
93+
criterion = { version = "0.4", optional = true, features = ["html_reports"] }
9494

9595
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
9696
tokio = { workspace = true, features = [

russh/src/kex/hybrid_mlkem.rs

Lines changed: 29 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,28 @@ use byteorder::{BigEndian, ByteOrder};
22
use curve25519_dalek::constants::ED25519_BASEPOINT_TABLE;
33
use curve25519_dalek::montgomery::MontgomeryPoint;
44
use curve25519_dalek::scalar::Scalar;
5-
use libcrux_ml_kem::mlkem768::{
6-
decapsulate, encapsulate, generate_key_pair, MlKem768Ciphertext, MlKem768PrivateKey,
7-
MlKem768PublicKey,
8-
};
9-
use libcrux_ml_kem::{KEY_GENERATION_SEED_SIZE, SHARED_SECRET_SIZE};
105
use log::debug;
6+
use ml_kem::{
7+
EncodedSizeUser, KemCore, MlKem768, MlKem768Params,
8+
kem::{Decapsulate, DecapsulationKey, Encapsulate, EncapsulationKey},
9+
};
1110
use sha2::Digest;
1211
use ssh_encoding::{Encode, Writer};
1312

14-
use super::{compute_keys, KexAlgorithm, KexAlgorithmImplementor, KexType, SharedSecret};
13+
use super::{KexAlgorithm, KexAlgorithmImplementor, KexType, SharedSecret, compute_keys};
14+
use crate::keys::ssh_key::rand_core::OsRng;
1515
use crate::mac;
1616
use crate::session::Exchange;
17-
use crate::{cipher, msg, CryptoVec, Error};
17+
use crate::{CryptoVec, Error, cipher, msg};
1818

1919
const MLKEM768_PUBLIC_KEY_SIZE: usize = 1184;
2020
const MLKEM768_CIPHERTEXT_SIZE: usize = 1088;
2121
const X25519_PUBLIC_KEY_SIZE: usize = 32;
2222

23+
type MlKem768PublicKey = EncapsulationKey<MlKem768Params>;
24+
type MlKem768PrivateKey = DecapsulationKey<MlKem768Params>;
25+
type MlKem768Ciphertext = ml_kem::Ciphertext<MlKem768>;
26+
2327
pub struct MlKem768X25519KexType {}
2428

2529
impl KexType for MlKem768X25519KexType {
@@ -38,7 +42,7 @@ impl KexType for MlKem768X25519KexType {
3842
pub struct MlKem768X25519Kex {
3943
mlkem_secret: Option<Box<MlKem768PrivateKey>>,
4044
x25519_secret: Option<Scalar>,
41-
k_pq: Option<[u8; SHARED_SECRET_SIZE]>,
45+
k_pq: Option<ml_kem::SharedKey<MlKem768>>,
4246
k_cl: Option<MontgomeryPoint>,
4347
}
4448

@@ -82,25 +86,25 @@ impl KexAlgorithmImplementor for MlKem768X25519Kex {
8286
#[allow(clippy::indexing_slicing)]
8387
let c_pk1_bytes = &c_init[MLKEM768_PUBLIC_KEY_SIZE..];
8488

85-
let mut c_pk2_array = [0u8; MLKEM768_PUBLIC_KEY_SIZE];
86-
c_pk2_array.copy_from_slice(c_pk2_bytes);
87-
let c_pk2 = MlKem768PublicKey::from(c_pk2_array);
89+
let c_pk2_array =
90+
ml_kem::Encoded::<MlKem768PublicKey>::try_from(c_pk2_bytes).map_err(|_| Error::Kex)?;
91+
let c_pk2 = MlKem768PublicKey::from_bytes(&c_pk2_array);
8892

8993
let mut c_pk1 = MontgomeryPoint([0; 32]);
9094
c_pk1.0.copy_from_slice(c_pk1_bytes);
9195

92-
let mut randomness = [0u8; SHARED_SECRET_SIZE];
93-
getrandom::getrandom(&mut randomness).map_err(|_| Error::KexInit)?;
94-
95-
let (s_ct2, k_pq_shared_secret) = encapsulate(&c_pk2, randomness);
96+
let (s_ct2, k_pq_shared_secret) =
97+
c_pk2.encapsulate(&mut OsRng).map_err(|_| Error::KexInit)?;
9698

9799
let s_secret = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
98100
let s_pk1 = (ED25519_BASEPOINT_TABLE * &s_secret).to_montgomery();
99101

100102
let k_cl = s_secret * c_pk1;
101103

102104
exchange.server_ephemeral.clear();
103-
exchange.server_ephemeral.extend_from_slice(s_ct2.as_slice());
105+
exchange
106+
.server_ephemeral
107+
.extend_from_slice(s_ct2.as_slice());
104108
exchange.server_ephemeral.extend_from_slice(&s_pk1.0);
105109

106110
self.k_pq = Some(k_pq_shared_secret);
@@ -114,22 +118,18 @@ impl KexAlgorithmImplementor for MlKem768X25519Kex {
114118
client_ephemeral: &mut Vec<u8>,
115119
writer: &mut impl Writer,
116120
) -> Result<(), Error> {
117-
let mut randomness = [0u8; KEY_GENERATION_SEED_SIZE];
118-
getrandom::getrandom(&mut randomness).map_err(|_| Error::KexInit)?;
119-
120-
let keypair = generate_key_pair(randomness);
121-
let (mlkem_sk, mlkem_pk) = keypair.into_parts();
121+
let (mlkem_sk, mlkem_pk) = MlKem768::generate(&mut OsRng);
122122

123123
let x25519_secret = Scalar::from_bytes_mod_order(rand::random::<[u8; 32]>());
124124
let x25519_pk = (ED25519_BASEPOINT_TABLE * &x25519_secret).to_montgomery();
125125

126126
client_ephemeral.clear();
127-
client_ephemeral.extend_from_slice(mlkem_pk.as_slice());
128-
client_ephemeral.extend_from_slice(&x25519_pk.0);
127+
client_ephemeral.extend(&mlkem_pk.as_bytes());
128+
client_ephemeral.extend(&x25519_pk.0);
129129

130130
msg::KEX_HYBRID_INIT.encode(writer)?;
131131
let mut c_init = Vec::<u8>::new();
132-
c_init.extend(mlkem_pk.as_slice());
132+
c_init.extend(mlkem_pk.as_bytes());
133133
c_init.extend(&x25519_pk.0);
134134
c_init.as_slice().encode(writer)?;
135135

@@ -149,12 +149,12 @@ impl KexAlgorithmImplementor for MlKem768X25519Kex {
149149
#[allow(clippy::indexing_slicing)]
150150
let s_pk1_bytes = &remote_pubkey_[MLKEM768_CIPHERTEXT_SIZE..];
151151

152-
let mut s_ct2_array = [0u8; MLKEM768_CIPHERTEXT_SIZE];
153-
s_ct2_array.copy_from_slice(s_ct2_bytes);
154-
let s_ct2 = MlKem768Ciphertext::from(s_ct2_array);
152+
let s_ct2 = MlKem768Ciphertext::try_from(s_ct2_bytes).map_err(|_| Error::KexInit)?;
155153

156154
let mlkem_secret = self.mlkem_secret.take().ok_or(Error::KexInit)?;
157-
let k_pq_shared_secret = decapsulate(&mlkem_secret, &s_ct2);
155+
let k_pq_shared_secret = mlkem_secret
156+
.decapsulate(&s_ct2)
157+
.map_err(|_| Error::KexInit)?;
158158

159159
let mut s_pk1 = MontgomeryPoint([0; 32]);
160160
s_pk1.0.copy_from_slice(s_pk1_bytes);
@@ -366,8 +366,7 @@ mod tests {
366366
.unwrap();
367367

368368
assert_eq!(
369-
client_hash,
370-
server_hash,
369+
client_hash, server_hash,
371370
"Exchange hashes should match between client and server"
372371
);
373372
assert_eq!(client_hash.len(), 32, "SHA-256 hash should be 32 bytes");

0 commit comments

Comments
 (0)