rust_bottle/ecdh.rs
1use crate::errors::{BottleError, Result};
2use crate::tpm::ECDHHandler;
3use p256::ecdh::EphemeralSecret;
4use p256::{PublicKey, SecretKey};
5use rand::{CryptoRng, RngCore};
6use x25519_dalek::{PublicKey as X25519PublicKey, StaticSecret};
7
8#[cfg(feature = "ml-kem")]
9use hybrid_array::{
10 sizes::{U1088, U1568},
11 Array,
12};
13#[cfg(feature = "ml-kem")]
14use ml_kem::{kem::Kem, EncodedSizeUser, KemCore, MlKem1024Params, MlKem768Params};
15#[cfg(feature = "ml-kem")]
16use zerocopy::AsBytes;
17
18/// ECDH encryption using P-256 elliptic curve.
19///
20/// This function performs Elliptic Curve Diffie-Hellman key exchange using
21/// the P-256 (secp256r1) curve. It generates an ephemeral key pair, computes
22/// a shared secret with the recipient's public key, derives an AES-256-GCM
23/// encryption key, and encrypts the plaintext.
24///
25/// # Arguments
26///
27/// * `rng` - A cryptographically secure random number generator
28/// * `plaintext` - The message to encrypt
29/// * `public_key` - The recipient's P-256 public key
30///
31/// # Returns
32///
33/// * `Ok(Vec<u8>)` - Encrypted data: ephemeral public key (65 bytes) + ciphertext
34/// * `Err(BottleError::Encryption)` - If encryption fails
35///
36/// # Format
37///
38/// The output format is: `[ephemeral_public_key (65 bytes)][encrypted_data]`
39///
40/// # Example
41///
42/// ```rust
43/// use rust_bottle::ecdh::ecdh_encrypt_p256;
44/// use rust_bottle::keys::EcdsaP256Key;
45/// use rand::rngs::OsRng;
46///
47/// let rng = &mut OsRng;
48/// let key = EcdsaP256Key::generate(rng);
49/// let pub_key = p256::PublicKey::from_sec1_bytes(&key.public_key_bytes()).unwrap();
50///
51/// let plaintext = b"Secret message";
52/// let ciphertext = ecdh_encrypt_p256(rng, plaintext, &pub_key).unwrap();
53/// ```
54pub fn ecdh_encrypt_p256<R: RngCore + CryptoRng>(
55 rng: &mut R,
56 plaintext: &[u8],
57 public_key: &PublicKey,
58) -> Result<Vec<u8>> {
59 let secret = EphemeralSecret::random(rng);
60 let shared_secret = secret.diffie_hellman(public_key);
61
62 // Derive encryption key from shared secret
63 // For p256 0.13, the shared secret is a SharedSecret type
64 // Extract shared secret bytes - raw_secret_bytes() returns a GenericArray
65 let shared_bytes = shared_secret.raw_secret_bytes();
66 // Convert to slice for key derivation (use as_ref() instead of deprecated as_slice())
67 let key = derive_key(shared_bytes.as_ref());
68
69 // Encrypt using AES-GCM (simplified - in production use proper AEAD)
70 let encrypted = encrypt_aes_gcm(&key, plaintext)?;
71
72 // Include ephemeral public key
73 let ephemeral_pub = secret.public_key();
74 let mut result = ephemeral_pub.to_sec1_bytes().to_vec();
75 result.extend_from_slice(&encrypted);
76
77 Ok(result)
78}
79
80/// ECDH decryption using P-256 elliptic curve.
81///
82/// This function decrypts data encrypted with `ecdh_encrypt_p256`. It extracts
83/// the ephemeral public key from the ciphertext, computes the shared secret
84/// using the recipient's private key, derives the AES-256-GCM key, and decrypts.
85///
86/// # Arguments
87///
88/// * `ciphertext` - Encrypted data: ephemeral public key (65 bytes) + ciphertext
89/// * `private_key` - The recipient's P-256 private key
90///
91/// # Returns
92///
93/// * `Ok(Vec<u8>)` - Decrypted plaintext
94/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
95/// * `Err(BottleError::Decryption)` - If decryption fails
96///
97/// # Example
98///
99/// ```rust
100/// use rust_bottle::ecdh::{ecdh_encrypt_p256, ecdh_decrypt_p256};
101/// use rust_bottle::keys::EcdsaP256Key;
102/// use rand::rngs::OsRng;
103/// use p256::elliptic_curve::sec1::FromEncodedPoint;
104///
105/// let rng = &mut OsRng;
106/// let key = EcdsaP256Key::generate(rng);
107/// let pub_key = p256::PublicKey::from_sec1_bytes(&key.public_key_bytes()).unwrap();
108/// let priv_key_bytes = key.private_key_bytes();
109/// let priv_key = p256::SecretKey::from_bytes(priv_key_bytes.as_slice().into()).unwrap();
110///
111/// let plaintext = b"Secret message";
112/// let ciphertext = ecdh_encrypt_p256(rng, plaintext, &pub_key).unwrap();
113/// let decrypted = ecdh_decrypt_p256(&ciphertext, &priv_key).unwrap();
114/// assert_eq!(decrypted, plaintext);
115/// ```
116pub fn ecdh_decrypt_p256(ciphertext: &[u8], private_key: &SecretKey) -> Result<Vec<u8>> {
117 if ciphertext.len() < 65 {
118 return Err(BottleError::InvalidFormat);
119 }
120
121 // Extract ephemeral public key
122 let ephemeral_pub = PublicKey::from_sec1_bytes(&ciphertext[..65])
123 .map_err(|_| BottleError::Decryption("Invalid ephemeral public key".to_string()))?;
124
125 // Compute shared secret using ECDH
126 // For p256 0.13, use the SecretKey with the ephemeral public key
127 // Create a SharedSecret by multiplying the private scalar with the public point
128 use p256::elliptic_curve::sec1::ToEncodedPoint;
129 let scalar = private_key.to_nonzero_scalar();
130 let point = ephemeral_pub.as_affine();
131 // Perform ECDH: shared_secret = private_scalar * public_point
132 let shared_point = (*point * scalar.as_ref()).to_encoded_point(false);
133 // Use x-coordinate as shared secret (standard ECDH) (use as_ref() instead of deprecated as_slice())
134 let shared_bytes = shared_point.x().unwrap().as_ref();
135 let key = derive_key(shared_bytes);
136
137 // Decrypt
138 decrypt_aes_gcm(&key, &ciphertext[65..])
139}
140
141/// X25519 ECDH encryption.
142///
143/// This function performs Elliptic Curve Diffie-Hellman key exchange using
144/// the X25519 curve (Curve25519). It generates an ephemeral key pair, computes
145/// a shared secret with the recipient's public key, derives an AES-256-GCM
146/// encryption key, and encrypts the plaintext.
147///
148/// # Arguments
149///
150/// * `rng` - A random number generator
151/// * `plaintext` - The message to encrypt
152/// * `public_key` - The recipient's X25519 public key
153///
154/// # Returns
155///
156/// * `Ok(Vec<u8>)` - Encrypted data: ephemeral public key (32 bytes) + ciphertext
157/// * `Err(BottleError::Encryption)` - If encryption fails
158///
159/// # Format
160///
161/// The output format is: `[ephemeral_public_key (32 bytes)][encrypted_data]`
162///
163/// # Example
164///
165/// ```rust
166/// use rust_bottle::ecdh::ecdh_encrypt_x25519;
167/// use rust_bottle::keys::X25519Key;
168/// use rand::rngs::OsRng;
169///
170/// let rng = &mut OsRng;
171/// let key = X25519Key::generate(rng);
172/// let pub_key_bytes: [u8; 32] = key.public_key_bytes().try_into().unwrap();
173/// let pub_key = x25519_dalek::PublicKey::from(pub_key_bytes);
174///
175/// let plaintext = b"Secret message";
176/// let ciphertext = ecdh_encrypt_x25519(rng, plaintext, &pub_key).unwrap();
177/// ```
178pub fn ecdh_encrypt_x25519<R: RngCore>(
179 rng: &mut R,
180 plaintext: &[u8],
181 public_key: &X25519PublicKey,
182) -> Result<Vec<u8>> {
183 // Generate random secret key (32 bytes for X25519)
184 let mut secret_bytes = [0u8; 32];
185 rng.fill_bytes(&mut secret_bytes);
186
187 // Use StaticSecret from x25519-dalek 1.0
188 let secret = StaticSecret::from(secret_bytes);
189
190 // Compute shared secret
191 let shared_secret = secret.diffie_hellman(public_key);
192
193 // Derive encryption key from shared secret
194 let key = derive_key(shared_secret.as_bytes());
195
196 // Encrypt
197 let encrypted = encrypt_aes_gcm(&key, plaintext)?;
198
199 // Get ephemeral public key
200 let ephemeral_pub = X25519PublicKey::from(&secret);
201
202 let mut result = ephemeral_pub.as_bytes().to_vec();
203 result.extend_from_slice(&encrypted);
204
205 Ok(result)
206}
207
208/// X25519 ECDH decryption.
209///
210/// This function decrypts data encrypted with `ecdh_encrypt_x25519`. It extracts
211/// the ephemeral public key from the ciphertext, computes the shared secret
212/// using the recipient's private key, derives the AES-256-GCM key, and decrypts.
213///
214/// # Arguments
215///
216/// * `ciphertext` - Encrypted data: ephemeral public key (32 bytes) + ciphertext
217/// * `private_key` - The recipient's X25519 private key (32 bytes)
218///
219/// # Returns
220///
221/// * `Ok(Vec<u8>)` - Decrypted plaintext
222/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
223/// * `Err(BottleError::Decryption)` - If decryption fails
224///
225/// # Example
226///
227/// ```rust
228/// use rust_bottle::ecdh::{ecdh_encrypt_x25519, ecdh_decrypt_x25519};
229/// use rust_bottle::keys::X25519Key;
230/// use rand::rngs::OsRng;
231///
232/// let rng = &mut OsRng;
233/// let key = X25519Key::generate(rng);
234/// let pub_key_bytes: [u8; 32] = key.public_key_bytes().try_into().unwrap();
235/// let pub_key = x25519_dalek::PublicKey::from(pub_key_bytes);
236/// let priv_key_bytes: [u8; 32] = key.private_key_bytes().try_into().unwrap();
237///
238/// let plaintext = b"Secret message";
239/// let ciphertext = ecdh_encrypt_x25519(rng, plaintext, &pub_key).unwrap();
240/// let decrypted = ecdh_decrypt_x25519(&ciphertext, &priv_key_bytes).unwrap();
241/// assert_eq!(decrypted, plaintext);
242/// ```
243pub fn ecdh_decrypt_x25519(ciphertext: &[u8], private_key: &[u8; 32]) -> Result<Vec<u8>> {
244 if ciphertext.len() < 32 {
245 return Err(BottleError::InvalidFormat);
246 }
247
248 // Create StaticSecret from private key bytes
249 let priv_key = StaticSecret::from(*private_key);
250
251 // Extract ephemeral public key (32 bytes)
252 let ephemeral_pub_bytes: [u8; 32] = ciphertext[..32]
253 .try_into()
254 .map_err(|_| BottleError::InvalidFormat)?;
255 let ephemeral_pub = X25519PublicKey::from(ephemeral_pub_bytes);
256
257 // Compute shared secret
258 let shared_secret = priv_key.diffie_hellman(&ephemeral_pub);
259 let key = derive_key(shared_secret.as_bytes());
260
261 // Decrypt
262 decrypt_aes_gcm(&key, &ciphertext[32..])
263}
264
265/// Trait for ECDH encryption operations.
266///
267/// This trait allows different ECDH implementations to be used polymorphically.
268/// Currently not used in the public API but available for extension.
269pub trait ECDHEncrypt {
270 /// Encrypt plaintext to a public key using ECDH.
271 fn encrypt<R: RngCore>(
272 &self,
273 rng: &mut R,
274 plaintext: &[u8],
275 public_key: &[u8],
276 ) -> Result<Vec<u8>>;
277}
278
279/// Trait for ECDH decryption operations.
280///
281/// This trait allows different ECDH implementations to be used polymorphically.
282/// Currently not used in the public API but available for extension.
283pub trait ECDHDecrypt {
284 /// Decrypt ciphertext using a private key.
285 fn decrypt(&self, ciphertext: &[u8], private_key: &[u8]) -> Result<Vec<u8>>;
286}
287
288/// Generic ECDH encryption function with automatic key type detection.
289///
290/// This function automatically detects the key type based on the public key
291/// length and format, then uses the appropriate encryption implementation.
292///
293/// # Key Type Detection
294///
295/// * 32 bytes: X25519 (Curve25519)
296/// * 64 or 65 bytes: P-256 (secp256r1) in SEC1 format
297/// * 1184 bytes: ML-KEM-768 public key
298/// * 1568 bytes: ML-KEM-1024 public key
299///
300/// # Arguments
301///
302/// * `rng` - A cryptographically secure random number generator
303/// * `plaintext` - The message to encrypt
304/// * `public_key` - The recipient's public key (any supported format)
305///
306/// # Returns
307///
308/// * `Ok(Vec<u8>)` - Encrypted data with ephemeral public key prepended
309/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
310/// * `Err(BottleError::Encryption)` - If encryption fails
311///
312/// # Example
313///
314/// ```rust
315/// use rust_bottle::ecdh::ecdh_encrypt;
316/// use rust_bottle::keys::X25519Key;
317/// use rand::rngs::OsRng;
318///
319/// let rng = &mut OsRng;
320/// let key = X25519Key::generate(rng);
321/// let plaintext = b"Secret message";
322///
323/// let ciphertext = ecdh_encrypt(rng, plaintext, &key.public_key_bytes()).unwrap();
324/// ```
325pub fn ecdh_encrypt<R: RngCore + CryptoRng>(
326 rng: &mut R,
327 plaintext: &[u8],
328 public_key: &[u8],
329) -> Result<Vec<u8>> {
330 ecdh_encrypt_with_handler(rng, plaintext, public_key, None)
331}
332
333/// Generic ECDH encryption function with optional TPM/HSM handler.
334///
335/// This function is similar to `ecdh_encrypt`. The handler parameter is currently
336/// unused for encryption (encryption always uses software-generated ephemeral keys),
337/// but is provided for API consistency. Handlers are primarily used during
338/// decryption when the recipient's private key is stored in TPM/HSM.
339///
340/// # Arguments
341///
342/// * `rng` - A cryptographically secure random number generator
343/// * `plaintext` - The message to encrypt
344/// * `public_key` - The recipient's public key (any supported format)
345/// * `handler` - Optional TPM/HSM handler (currently unused for encryption)
346///
347/// # Returns
348///
349/// * `Ok(Vec<u8>)` - Encrypted data with ephemeral public key prepended
350/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
351/// * `Err(BottleError::Encryption)` - If encryption fails
352///
353/// # Example
354///
355/// ```rust
356/// use rust_bottle::ecdh::ecdh_encrypt_with_handler;
357/// use rust_bottle::keys::X25519Key;
358/// use rand::rngs::OsRng;
359///
360/// let rng = &mut OsRng;
361/// let key = X25519Key::generate(rng);
362/// let plaintext = b"Secret message";
363///
364/// // Encryption always uses software implementation
365/// let ciphertext = ecdh_encrypt_with_handler(
366/// rng,
367/// plaintext,
368/// &key.public_key_bytes(),
369/// None,
370/// ).unwrap();
371/// ```
372pub fn ecdh_encrypt_with_handler<R: RngCore + CryptoRng>(
373 rng: &mut R,
374 plaintext: &[u8],
375 public_key: &[u8],
376 _handler: Option<&dyn ECDHHandler>,
377) -> Result<Vec<u8>> {
378 // Encryption always uses software implementation with ephemeral keys
379 // Handlers are used during decryption when the recipient's key is in TPM/HSM
380 // Try to determine key type and use appropriate function
381 // X25519 keys are 32 bytes
382 if public_key.len() == 32 {
383 let pub_key_bytes: [u8; 32] = public_key
384 .try_into()
385 .map_err(|_| BottleError::InvalidKeyType)?;
386 let pub_key = X25519PublicKey::from(pub_key_bytes);
387 ecdh_encrypt_x25519(rng, plaintext, &pub_key)
388 } else if public_key.len() == 65 || public_key.len() == 64 {
389 let pub_key =
390 PublicKey::from_sec1_bytes(public_key).map_err(|_| BottleError::InvalidKeyType)?;
391 ecdh_encrypt_p256(rng, plaintext, &pub_key)
392 } else {
393 #[cfg(feature = "ml-kem")]
394 {
395 if public_key.len() == 1184 {
396 // ML-KEM-768 public key
397 return mlkem768_encrypt(rng, plaintext, public_key);
398 } else if public_key.len() == 1568 {
399 // ML-KEM-1024 public key
400 return mlkem1024_encrypt(rng, plaintext, public_key);
401 }
402 }
403 Err(BottleError::InvalidKeyType)
404 }
405}
406
407/// Generic ECDH decryption function with automatic key type detection.
408///
409/// This function automatically detects the key type and uses the appropriate
410/// decryption implementation. It tries X25519 first, then P-256, then ML-KEM.
411///
412/// # Key Type Detection
413///
414/// * 32 bytes: Tries X25519 first, then P-256 if X25519 fails
415/// * 2400 bytes: ML-KEM-768 secret key
416/// * 3168 bytes: ML-KEM-1024 secret key
417///
418/// # Arguments
419///
420/// * `ciphertext` - Encrypted data with ephemeral public key prepended
421/// * `private_key` - The recipient's private key
422///
423/// # Returns
424///
425/// * `Ok(Vec<u8>)` - Decrypted plaintext
426/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
427/// * `Err(BottleError::Decryption)` - If decryption fails
428///
429/// # Example
430///
431/// ```rust
432/// use rust_bottle::ecdh::{ecdh_encrypt, ecdh_decrypt};
433/// use rust_bottle::keys::X25519Key;
434/// use rand::rngs::OsRng;
435///
436/// let rng = &mut OsRng;
437/// let key = X25519Key::generate(rng);
438/// let plaintext = b"Secret message";
439///
440/// let ciphertext = ecdh_encrypt(rng, plaintext, &key.public_key_bytes()).unwrap();
441/// let decrypted = ecdh_decrypt(&ciphertext, &key.private_key_bytes()).unwrap();
442/// assert_eq!(decrypted, plaintext);
443/// ```
444pub fn ecdh_decrypt(ciphertext: &[u8], private_key: &[u8]) -> Result<Vec<u8>> {
445 ecdh_decrypt_with_handler(ciphertext, private_key, None)
446}
447
448/// Generic ECDH decryption function with optional TPM/HSM handler.
449///
450/// This function is similar to `ecdh_decrypt`, but allows specifying a TPM/HSM
451/// handler for hardware-backed key operations. If a handler is provided, it will
452/// use the handler's private key (stored in TPM/HSM) for ECDH operations instead
453/// of the provided private_key parameter.
454///
455/// # Arguments
456///
457/// * `ciphertext` - Encrypted data with ephemeral public key prepended
458/// * `private_key` - The recipient's private key (ignored if handler is provided)
459/// * `handler` - Optional TPM/HSM handler for hardware-backed operations
460///
461/// # Returns
462///
463/// * `Ok(Vec<u8>)` - Decrypted plaintext
464/// * `Err(BottleError::InvalidKeyType)` - If the key format is not recognized
465/// * `Err(BottleError::Decryption)` - If decryption fails
466///
467/// # Example
468///
469/// ```rust,no_run
470/// use rust_bottle::ecdh::ecdh_decrypt_with_handler;
471///
472/// // Without handler (software implementation)
473/// // let decrypted = ecdh_decrypt_with_handler(&ciphertext, &private_key, None)?;
474///
475/// // With TPM handler (if available)
476/// // The handler's private key (stored in TPM) will be used for decryption
477/// // let tpm_handler = TpmHandler::new()?;
478/// // let decrypted = ecdh_decrypt_with_handler(&ciphertext, &[], Some(&tpm_handler))?;
479/// ```
480pub fn ecdh_decrypt_with_handler(
481 ciphertext: &[u8],
482 private_key: &[u8],
483 handler: Option<&dyn ECDHHandler>,
484) -> Result<Vec<u8>> {
485 // If a handler is provided, use it for decryption
486 if let Some(h) = handler {
487 // Get the handler's public key to determine key type and ephemeral key size
488 let handler_pub_key = h.public_key()?;
489
490 // Extract ephemeral public key from ciphertext
491 // The format depends on the key type (32 bytes for X25519, 65 for P-256)
492 if ciphertext.len() < handler_pub_key.len() {
493 return Err(BottleError::InvalidFormat);
494 }
495
496 let ephemeral_pub_key = &ciphertext[..handler_pub_key.len()];
497 let encrypted_data = &ciphertext[handler_pub_key.len()..];
498
499 // Use handler for ECDH (uses TPM/HSM private key)
500 let shared_secret = h.ecdh(ephemeral_pub_key)?;
501 let key = derive_key(&shared_secret);
502
503 // Decrypt
504 return decrypt_aes_gcm(&key, encrypted_data);
505 }
506
507 // Fall back to software implementation
508 #[cfg(feature = "ml-kem")]
509 {
510 // Try ML-KEM-768 (2400 bytes decapsulation key, or 3584 bytes full private key)
511 if private_key.len() == 2400 || private_key.len() == 3584 {
512 if let Ok(result) = mlkem768_decrypt(ciphertext, private_key) {
513 return Ok(result);
514 }
515 }
516
517 // Try ML-KEM-1024 (3168 bytes decapsulation key, or 4736 bytes full private key)
518 if private_key.len() == 3168 || private_key.len() == 4736 {
519 if let Ok(result) = mlkem1024_decrypt(ciphertext, private_key) {
520 return Ok(result);
521 }
522 }
523 }
524
525 // Try X25519 first (32 bytes)
526 if private_key.len() == 32 && ciphertext.len() >= 32 {
527 // Try to create X25519 key
528 let priv_key_bytes: [u8; 32] = match private_key.try_into() {
529 Ok(bytes) => bytes,
530 Err(_) => return Err(BottleError::InvalidKeyType),
531 };
532 match ecdh_decrypt_x25519(ciphertext, &priv_key_bytes) {
533 Ok(result) => return Ok(result),
534 Err(_) => {
535 // Not X25519, try P-256
536 }
537 }
538 }
539
540 // Try P-256 (32 bytes private key, but different format)
541 // P-256 keys are also 32 bytes, so we need to try both
542 if private_key.len() == 32 {
543 if let Ok(priv_key) = SecretKey::from_bytes(private_key.into()) {
544 if let Ok(result) = ecdh_decrypt_p256(ciphertext, &priv_key) {
545 return Ok(result);
546 }
547 }
548 }
549
550 Err(BottleError::InvalidKeyType)
551}
552
553#[cfg(feature = "ml-kem")]
554/// ML-KEM-768 encryption (post-quantum).
555///
556/// This function performs ML-KEM key encapsulation and encrypts the plaintext
557/// using the derived shared secret with AES-256-GCM.
558///
559/// # Arguments
560///
561/// * `rng` - A cryptographically secure random number generator (not used, ML-KEM is deterministic)
562/// * `plaintext` - The message to encrypt
563/// * `public_key` - The recipient's ML-KEM-768 public key (1184 bytes)
564///
565/// # Returns
566///
567/// * `Ok(Vec<u8>)` - Encrypted data: ML-KEM ciphertext (1088 bytes) + AES-GCM encrypted message
568/// * `Err(BottleError::Encryption)` - If encryption fails
569/// * `Err(BottleError::InvalidKeyType)` - If the key format is invalid
570pub fn mlkem768_encrypt<R: RngCore + CryptoRng>(
571 rng: &mut R,
572 plaintext: &[u8],
573 public_key: &[u8],
574) -> Result<Vec<u8>> {
575 // Parse public key (encapsulation key)
576 // from_bytes expects a generic-array Array type, so we need to convert
577 if public_key.len() != 1184 {
578 return Err(BottleError::InvalidKeyType);
579 }
580 let pub_key_array: [u8; 1184] = public_key
581 .try_into()
582 .map_err(|_| BottleError::InvalidKeyType)?;
583 let ek =
584 <Kem<MlKem768Params> as KemCore>::EncapsulationKey::from_bytes((&pub_key_array).into());
585
586 // ml-kem uses rand_core 0.9, create adapter
587 use rand_core_09::{CryptoRng as CryptoRng09, RngCore as RngCore09};
588 struct RngAdapter<'a, R: RngCore + CryptoRng>(&'a mut R);
589 impl<'a, R: RngCore + CryptoRng> RngCore09 for RngAdapter<'a, R> {
590 fn next_u32(&mut self) -> u32 {
591 self.0.next_u32()
592 }
593 fn next_u64(&mut self) -> u64 {
594 self.0.next_u64()
595 }
596 fn fill_bytes(&mut self, dest: &mut [u8]) {
597 self.0.fill_bytes(dest)
598 }
599 // try_fill_bytes has a default implementation that calls fill_bytes, so we don't need to implement it
600 }
601 impl<'a, R: RngCore + CryptoRng> CryptoRng09 for RngAdapter<'a, R> {}
602
603 let mut adapter = RngAdapter(rng);
604 // Encapsulate (generate shared secret and ciphertext)
605 use ml_kem::kem::Encapsulate;
606 let (ct, shared_secret) = ek
607 .encapsulate(&mut adapter)
608 .map_err(|_| BottleError::Encryption("ML-KEM encapsulation failed".to_string()))?;
609
610 // Derive AES key from shared secret (shared_secret is [u8; 32])
611 let key = derive_key(&shared_secret);
612
613 // Encrypt plaintext with AES-GCM
614 let encrypted = encrypt_aes_gcm(&key, plaintext)?;
615
616 // Combine: ML-KEM ciphertext + AES-GCM encrypted data
617 let mut result = ct.as_bytes().to_vec();
618 result.extend_from_slice(&encrypted);
619
620 Ok(result)
621}
622
623/// ML-KEM-768 decryption (post-quantum).
624///
625/// # Arguments
626///
627/// * `ciphertext` - Encrypted data: ML-KEM ciphertext (1088 bytes) + AES-GCM encrypted message
628/// * `secret_key` - The recipient's ML-KEM-768 secret key (2400 bytes)
629///
630/// # Returns
631///
632/// * `Ok(Vec<u8>)` - Decrypted plaintext
633/// * `Err(BottleError::Decryption)` - If decryption fails
634/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
635#[cfg(feature = "ml-kem")]
636pub fn mlkem768_decrypt(ciphertext: &[u8], secret_key: &[u8]) -> Result<Vec<u8>> {
637 // Parse secret key (decapsulation key)
638 // Accept either 2400 bytes (decapsulation key only) or 3584 bytes (full private key: decaps + encaps)
639 let dk_bytes = if secret_key.len() == 2400 {
640 secret_key
641 } else if secret_key.len() == 3584 {
642 // Extract decapsulation key from full private key (first 2400 bytes)
643 &secret_key[..2400]
644 } else {
645 return Err(BottleError::InvalidKeyType);
646 };
647 let sec_key_array: [u8; 2400] = dk_bytes
648 .try_into()
649 .map_err(|_| BottleError::InvalidKeyType)?;
650 let dk =
651 <Kem<MlKem768Params> as KemCore>::DecapsulationKey::from_bytes((&sec_key_array).into());
652
653 // Extract ML-KEM ciphertext (first 1088 bytes for ML-KEM-768)
654 const CT_SIZE: usize = 1088; // ML-KEM-768 ciphertext size
655 // AES-GCM needs at least 12 bytes (nonce) + 16 bytes (tag) = 28 bytes minimum
656 if ciphertext.len() < CT_SIZE + 28 {
657 return Err(BottleError::InvalidFormat);
658 }
659 // Extract exactly CT_SIZE bytes for the ML-KEM ciphertext
660 let mlkem_ct_bytes = &ciphertext[..CT_SIZE];
661 let ct_array: [u8; CT_SIZE] = mlkem_ct_bytes
662 .try_into()
663 .map_err(|_| BottleError::InvalidFormat)?;
664 // Ciphertext type: use Array with size constant from hybrid_array::sizes
665 // ML-KEM-768 ciphertext is 1088 bytes
666 // Array implements From<[T; N]>, so we pass the array by value
667 let mlkem_ct: Array<u8, U1088> = ct_array.into();
668 let aes_ct = &ciphertext[CT_SIZE..];
669
670 // Decapsulate to get shared secret
671 use ml_kem::kem::Decapsulate;
672 let shared_secret = dk
673 .decapsulate(&mlkem_ct)
674 .map_err(|_| BottleError::Decryption("ML-KEM decapsulation failed".to_string()))?;
675
676 // Derive AES key (shared_secret is [u8; 32])
677 let key = derive_key(&shared_secret);
678
679 // Decrypt with AES-GCM
680 decrypt_aes_gcm(&key, aes_ct)
681}
682
683/// ML-KEM-1024 encryption (post-quantum).
684///
685/// # Arguments
686///
687/// * `rng` - A cryptographically secure random number generator (not used)
688/// * `plaintext` - The message to encrypt
689/// * `public_key` - The recipient's ML-KEM-1024 public key (1568 bytes)
690///
691/// # Returns
692///
693/// * `Ok(Vec<u8>)` - Encrypted data: ML-KEM ciphertext (1568 bytes) + AES-GCM encrypted message
694#[cfg(feature = "ml-kem")]
695pub fn mlkem1024_encrypt<R: RngCore + CryptoRng>(
696 rng: &mut R,
697 plaintext: &[u8],
698 public_key: &[u8],
699) -> Result<Vec<u8>> {
700 // Parse public key (encapsulation key)
701 if public_key.len() != 1568 {
702 return Err(BottleError::InvalidKeyType);
703 }
704 let pub_key_array: [u8; 1568] = public_key
705 .try_into()
706 .map_err(|_| BottleError::InvalidKeyType)?;
707 let ek =
708 <Kem<MlKem1024Params> as KemCore>::EncapsulationKey::from_bytes((&pub_key_array).into());
709
710 // ml-kem uses rand_core 0.9, create adapter
711 use rand_core_09::{CryptoRng as CryptoRng09, RngCore as RngCore09};
712 struct RngAdapter<'a, R: RngCore + CryptoRng>(&'a mut R);
713 impl<'a, R: RngCore + CryptoRng> RngCore09 for RngAdapter<'a, R> {
714 fn next_u32(&mut self) -> u32 {
715 self.0.next_u32()
716 }
717 fn next_u64(&mut self) -> u64 {
718 self.0.next_u64()
719 }
720 fn fill_bytes(&mut self, dest: &mut [u8]) {
721 self.0.fill_bytes(dest)
722 }
723 // try_fill_bytes has a default implementation that calls fill_bytes, so we don't need to implement it
724 }
725 impl<'a, R: RngCore + CryptoRng> CryptoRng09 for RngAdapter<'a, R> {}
726
727 let mut adapter = RngAdapter(rng);
728 // Encapsulate (generate shared secret and ciphertext)
729 use ml_kem::kem::Encapsulate;
730 let (ct, shared_secret) = ek
731 .encapsulate(&mut adapter)
732 .map_err(|_| BottleError::Encryption("ML-KEM encapsulation failed".to_string()))?;
733
734 // Derive AES key from shared secret (shared_secret is [u8; 32])
735 let key = derive_key(&shared_secret);
736
737 // Encrypt plaintext with AES-GCM
738 let encrypted = encrypt_aes_gcm(&key, plaintext)?;
739
740 // Combine: ML-KEM ciphertext + AES-GCM encrypted data
741 let mut result = ct.as_bytes().to_vec();
742 result.extend_from_slice(&encrypted);
743 Ok(result)
744}
745
746/// ML-KEM-1024 decryption (post-quantum).
747///
748/// # Arguments
749///
750/// * `ciphertext` - Encrypted data: ML-KEM ciphertext (1568 bytes) + AES-GCM encrypted message
751/// * `secret_key` - The recipient's ML-KEM-1024 secret key (3168 bytes)
752///
753/// # Returns
754///
755/// * `Ok(Vec<u8>)` - Decrypted plaintext
756#[cfg(feature = "ml-kem")]
757pub fn mlkem1024_decrypt(ciphertext: &[u8], secret_key: &[u8]) -> Result<Vec<u8>> {
758 // Parse secret key (decapsulation key)
759 // Accept either 3168 bytes (decapsulation key only) or 4736 bytes (full private key: decaps + encaps)
760 let dk_bytes = if secret_key.len() == 3168 {
761 secret_key
762 } else if secret_key.len() == 4736 {
763 // Extract decapsulation key from full private key (first 3168 bytes)
764 &secret_key[..3168]
765 } else {
766 return Err(BottleError::InvalidKeyType);
767 };
768 let sec_key_array: [u8; 3168] = dk_bytes
769 .try_into()
770 .map_err(|_| BottleError::InvalidKeyType)?;
771 let dk =
772 <Kem<MlKem1024Params> as KemCore>::DecapsulationKey::from_bytes((&sec_key_array).into());
773
774 // Extract ML-KEM ciphertext (first 1568 bytes for ML-KEM-1024)
775 const CT_SIZE: usize = 1568; // ML-KEM-1024 ciphertext size
776 // AES-GCM needs at least 12 bytes (nonce) + 16 bytes (tag) = 28 bytes minimum
777 if ciphertext.len() < CT_SIZE + 28 {
778 return Err(BottleError::InvalidFormat);
779 }
780 let ct_array: [u8; CT_SIZE] = ciphertext[..CT_SIZE]
781 .try_into()
782 .map_err(|_| BottleError::InvalidFormat)?;
783 // Ciphertext type: use Array with size constant from hybrid_array::sizes
784 // ML-KEM-1024 ciphertext is 1568 bytes
785 // Array implements From<[T; N]>, so we pass the array by value
786 let mlkem_ct: Array<u8, U1568> = ct_array.into();
787 let aes_ct = &ciphertext[CT_SIZE..];
788
789 // Decapsulate to get shared secret
790 use ml_kem::kem::Decapsulate;
791 let shared_secret = dk
792 .decapsulate(&mlkem_ct)
793 .map_err(|_| BottleError::Decryption("ML-KEM decapsulation failed".to_string()))?;
794
795 // Derive AES key (shared_secret is [u8; 32])
796 let key = derive_key(&shared_secret);
797 decrypt_aes_gcm(&key, aes_ct)
798}
799
800/// Hybrid encryption: ML-KEM-768 + X25519.
801///
802/// This provides both post-quantum and classical security by combining
803/// ML-KEM and X25519 key exchange. The plaintext is encrypted with both
804/// algorithms, and either can be used for decryption.
805///
806/// # Arguments
807///
808/// * `rng` - A cryptographically secure random number generator
809/// * `plaintext` - The message to encrypt
810/// * `mlkem_pub` - ML-KEM-768 public key (1184 bytes)
811/// * `x25519_pub` - X25519 public key (32 bytes)
812///
813/// # Returns
814///
815/// * `Ok(Vec<u8>)` - Encrypted data: [mlkem_len: u32][mlkem_ct][x25519_ct]
816#[cfg(feature = "ml-kem")]
817pub fn hybrid_encrypt_mlkem768_x25519<R: RngCore + CryptoRng>(
818 rng: &mut R,
819 plaintext: &[u8],
820 mlkem_pub: &[u8],
821 x25519_pub: &[u8],
822) -> Result<Vec<u8>> {
823 // Encrypt with both ML-KEM and X25519
824 let mlkem_ct = mlkem768_encrypt(rng, plaintext, mlkem_pub)?;
825 let x25519_pub_bytes: [u8; 32] = x25519_pub
826 .try_into()
827 .map_err(|_| BottleError::InvalidKeyType)?;
828 let x25519_pub_key = X25519PublicKey::from(x25519_pub_bytes);
829 let x25519_ct = ecdh_encrypt_x25519(rng, plaintext, &x25519_pub_key)?;
830
831 // Combine: ML-KEM ciphertext + X25519 ciphertext
832 // Format: [mlkem_len: u32][mlkem_ct][x25519_ct]
833 let mut result = Vec::new();
834 result.extend_from_slice(&(mlkem_ct.len() as u32).to_le_bytes());
835 result.extend_from_slice(&mlkem_ct);
836 result.extend_from_slice(&x25519_ct);
837
838 Ok(result)
839}
840
841#[cfg(feature = "ml-kem")]
842/// Hybrid decryption: ML-KEM-768 + X25519.
843///
844/// Attempts to decrypt using ML-KEM first, then falls back to X25519.
845///
846/// # Arguments
847///
848/// * `ciphertext` - Encrypted data: [mlkem_len: u32][mlkem_ct][x25519_ct]
849/// * `mlkem_sec` - ML-KEM-768 secret key (2400 bytes)
850/// * `x25519_sec` - X25519 secret key (32 bytes)
851///
852/// # Returns
853///
854/// * `Ok(Vec<u8>)` - Decrypted plaintext
855pub fn hybrid_decrypt_mlkem768_x25519(
856 ciphertext: &[u8],
857 mlkem_sec: &[u8],
858 x25519_sec: &[u8; 32],
859) -> Result<Vec<u8>> {
860 if ciphertext.len() < 4 {
861 return Err(BottleError::InvalidFormat);
862 }
863
864 // Extract lengths
865 let mlkem_len = u32::from_le_bytes(ciphertext[..4].try_into().unwrap()) as usize;
866 if ciphertext.len() < 4 + mlkem_len {
867 return Err(BottleError::InvalidFormat);
868 }
869
870 let mlkem_ct = &ciphertext[4..4 + mlkem_len];
871 let x25519_ct = &ciphertext[4 + mlkem_len..];
872
873 // Try ML-KEM first, fall back to X25519
874 match mlkem768_decrypt(mlkem_ct, mlkem_sec) {
875 Ok(plaintext) => Ok(plaintext),
876 Err(_) => ecdh_decrypt_x25519(x25519_ct, x25519_sec),
877 }
878}
879
880// Helper functions
881
882/// Derive a 32-byte encryption key from a shared secret using SHA-256.
883///
884/// This function uses SHA-256 to derive a deterministic encryption key
885/// from the ECDH shared secret. The output is always 32 bytes, suitable
886/// for AES-256.
887///
888/// # Arguments
889///
890/// * `shared_secret` - The ECDH shared secret bytes
891///
892/// # Returns
893///
894/// A 32-byte array containing the derived key
895fn derive_key(shared_secret: &[u8]) -> [u8; 32] {
896 use sha2::Digest;
897 use sha2::Sha256;
898 let mut hasher = Sha256::new();
899 hasher.update(shared_secret);
900 let hash = hasher.finalize();
901 let mut key = [0u8; 32];
902 key.copy_from_slice(&hash);
903 key
904}
905
906/// Encrypt plaintext using AES-256-GCM authenticated encryption.
907///
908/// This function uses AES-256-GCM for authenticated encryption with
909/// associated data (AEAD). It generates a random 12-byte nonce and
910/// prepends it to the ciphertext.
911///
912/// # Arguments
913///
914/// * `key` - 32-byte AES-256 key
915/// * `plaintext` - The message to encrypt
916///
917/// # Returns
918///
919/// * `Ok(Vec<u8>)` - Encrypted data: nonce (12 bytes) + ciphertext + tag (16 bytes)
920/// * `Err(BottleError::Encryption)` - If encryption fails
921///
922/// # Format
923///
924/// The output format is: `[nonce (12 bytes)][ciphertext + tag (16 bytes)]`
925///
926/// # Security Note
927///
928/// The nonce is randomly generated for each encryption operation. The
929/// authentication tag is automatically appended by the GCM mode.
930fn encrypt_aes_gcm(key: &[u8; 32], plaintext: &[u8]) -> Result<Vec<u8>> {
931 use ring::aead::{self, BoundKey, NonceSequence, UnboundKey};
932 use ring::rand::{SecureRandom, SystemRandom};
933
934 let rng = SystemRandom::new();
935 let mut nonce_bytes = [0u8; 12];
936 rng.fill(&mut nonce_bytes)
937 .map_err(|_| BottleError::Encryption("RNG failure".to_string()))?;
938
939 let _nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
940
941 let unbound_key = UnboundKey::new(&aead::AES_256_GCM, key)
942 .map_err(|_| BottleError::Encryption("Key creation failed".to_string()))?;
943
944 struct SingleNonceSequence([u8; 12]);
945 impl NonceSequence for SingleNonceSequence {
946 fn advance(&mut self) -> std::result::Result<aead::Nonce, ring::error::Unspecified> {
947 Ok(aead::Nonce::assume_unique_for_key(self.0))
948 }
949 }
950
951 let mut sealing_key = aead::SealingKey::new(unbound_key, SingleNonceSequence(nonce_bytes));
952
953 // seal_in_place_append_tag encrypts the data in the buffer and appends the tag.
954 // According to ring docs, the buffer must have enough capacity for the tag.
955 // The function will extend the vector to append the tag.
956 let mut in_out = plaintext.to_vec();
957 // Reserve capacity for the tag (ring will extend the vector when appending)
958 let tag_len = sealing_key.algorithm().tag_len();
959 in_out.reserve(tag_len);
960
961 // seal_in_place_append_tag encrypts the plaintext and appends the tag
962 // It requires the vector to have enough capacity, which we've reserved
963 sealing_key
964 .seal_in_place_append_tag(aead::Aad::empty(), &mut in_out)
965 .map_err(|_| BottleError::Encryption("Encryption failed".to_string()))?;
966
967 // Prepend nonce to the result
968 let mut result = nonce_bytes.to_vec();
969 result.extend_from_slice(&in_out);
970 Ok(result)
971}
972
973/// Decrypt ciphertext using AES-256-GCM authenticated encryption.
974///
975/// This function decrypts data encrypted with `encrypt_aes_gcm`. It extracts
976/// the nonce, verifies the authentication tag, and returns the plaintext.
977///
978/// # Arguments
979///
980/// * `key` - 32-byte AES-256 key (same as used for encryption)
981/// * `ciphertext` - Encrypted data: nonce (12 bytes) + ciphertext + tag (16 bytes)
982///
983/// # Returns
984///
985/// * `Ok(Vec<u8>)` - Decrypted plaintext (with padding zeros removed if present)
986/// * `Err(BottleError::InvalidFormat)` - If ciphertext is too short
987/// * `Err(BottleError::Decryption)` - If decryption or authentication fails
988///
989/// # Security Note
990///
991/// This function automatically verifies the authentication tag. If verification
992/// fails, decryption returns an error. The function also trims trailing zeros
993/// that may have been added during encryption for tag space.
994fn decrypt_aes_gcm(key: &[u8; 32], ciphertext: &[u8]) -> Result<Vec<u8>> {
995 use ring::aead::{self, BoundKey, NonceSequence, OpeningKey, UnboundKey};
996
997 if ciphertext.len() < 12 {
998 return Err(BottleError::InvalidFormat);
999 }
1000
1001 let nonce_bytes: [u8; 12] = ciphertext[..12]
1002 .try_into()
1003 .map_err(|_| BottleError::Decryption("Invalid nonce length".to_string()))?;
1004 let _nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
1005
1006 let unbound_key = UnboundKey::new(&aead::AES_256_GCM, key)
1007 .map_err(|_| BottleError::Decryption("Key creation failed".to_string()))?;
1008
1009 struct SingleNonceSequence([u8; 12]);
1010 impl NonceSequence for SingleNonceSequence {
1011 fn advance(&mut self) -> std::result::Result<aead::Nonce, ring::error::Unspecified> {
1012 Ok(aead::Nonce::assume_unique_for_key(self.0))
1013 }
1014 }
1015
1016 let mut opening_key = OpeningKey::new(unbound_key, SingleNonceSequence(nonce_bytes));
1017
1018 // The ciphertext format is: nonce (12 bytes) + encrypted_data + tag (16 bytes)
1019 // open_in_place expects the ciphertext + tag (without the nonce)
1020 let mut in_out = ciphertext[12..].to_vec();
1021
1022 // open_in_place decrypts and verifies the tag, returning the plaintext
1023 // It expects the tag to be at the end of the buffer
1024 let plaintext = opening_key
1025 .open_in_place(aead::Aad::empty(), &mut in_out)
1026 .map_err(|_| BottleError::Decryption("Decryption failed".to_string()))?;
1027
1028 Ok(plaintext.to_vec())
1029}
1030
1031/// RSA-OAEP encryption.
1032///
1033/// This function encrypts data using RSA-OAEP (Optimal Asymmetric Encryption
1034/// Padding) with SHA-256. RSA can only encrypt small amounts of data (typically
1035/// up to key_size - 42 bytes for OAEP with SHA-256). For larger messages,
1036/// consider using RSA to encrypt a symmetric key and then encrypt the message
1037/// with that key.
1038///
1039/// # Arguments
1040///
1041/// * `rng` - A cryptographically secure random number generator
1042/// * `plaintext` - The message to encrypt (must be smaller than key_size - 42 bytes)
1043/// * `public_key` - The recipient's RSA public key
1044///
1045/// # Returns
1046///
1047/// * `Ok(Vec<u8>)` - Encrypted ciphertext
1048/// * `Err(BottleError::Encryption)` - If encryption fails
1049///
1050/// # Example
1051///
1052/// ```rust
1053/// use rust_bottle::ecdh::rsa_encrypt;
1054/// use rust_bottle::keys::RsaKey;
1055/// use rand::rngs::OsRng;
1056///
1057/// let rng = &mut OsRng;
1058/// let key = RsaKey::generate(rng, 2048).unwrap();
1059/// let plaintext = b"Small message";
1060///
1061/// let ciphertext = rsa_encrypt(rng, plaintext, key.public_key()).unwrap();
1062/// ```
1063pub fn rsa_encrypt<R: RngCore + CryptoRng>(
1064 rng: &mut R,
1065 plaintext: &[u8],
1066 public_key: &rsa::RsaPublicKey,
1067) -> Result<Vec<u8>> {
1068 use rsa::Oaep;
1069 use sha2::Sha256;
1070
1071 // RSA-OAEP with SHA-256
1072 let padding = Oaep::new::<Sha256>();
1073 public_key
1074 .encrypt(rng, padding, plaintext)
1075 .map_err(|e| BottleError::Encryption(format!("RSA encryption failed: {}", e)))
1076}
1077
1078/// RSA-OAEP decryption.
1079///
1080/// This function decrypts data encrypted with RSA-OAEP.
1081///
1082/// # Arguments
1083///
1084/// * `ciphertext` - The encrypted data
1085/// * `rsa_key` - The recipient's RSA key (as RsaKey)
1086///
1087/// # Returns
1088///
1089/// * `Ok(Vec<u8>)` - Decrypted plaintext
1090/// * `Err(BottleError::Decryption)` - If decryption fails
1091///
1092/// # Example
1093///
1094/// ```rust
1095/// use rust_bottle::ecdh::{rsa_encrypt, rsa_decrypt};
1096/// use rust_bottle::keys::RsaKey;
1097/// use rand::rngs::OsRng;
1098///
1099/// let rng = &mut OsRng;
1100/// let key = RsaKey::generate(rng, 2048).unwrap();
1101/// let plaintext = b"Small message";
1102///
1103/// let ciphertext = rsa_encrypt(rng, plaintext, key.public_key()).unwrap();
1104/// let decrypted = rsa_decrypt(&ciphertext, &key).unwrap();
1105/// assert_eq!(decrypted, plaintext);
1106/// ```
1107pub fn rsa_decrypt(ciphertext: &[u8], rsa_key: &crate::keys::RsaKey) -> Result<Vec<u8>> {
1108 rsa_key.decrypt(ciphertext)
1109}