Skip to content

Commit 0294544

Browse files
authored
ssh-cipher: add ChaCha20Poly1305 packet encryption example (#559)
The `ChaCha20Poly1305` type provided by this crate just implements the AEAD portion of the full construction. In a packet encryption context, ChaCha20Poly1305 is keyed by a 512-bit key consisting of two independent 256-bit ChaCha20 keys: one used for length encryption with the unauthenticated `ChaCha20`, and the other for authenticating the length ciphertext along with a body using the full `ChaCha20Poly1305` AEAD. This adds a code example showing how that can be done using only types imported from `ssh-cipher` and no additional dependencies. We could eventually wrap this up into a higher level API provided by `ssh-cipher` implementing this functionality, but for now a simple code example seems more flexible as far as the need to interleave length decryption with decoding the packet.
1 parent 5979a99 commit 0294544

2 files changed

Lines changed: 67 additions & 0 deletions

File tree

ssh-cipher/src/chacha20poly1305.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,71 @@ pub type ChaChaNonce = chacha20::LegacyNonce;
3535
/// - In the context of SSH packet encryption, AAD will be 4 bytes and contain the encrypted length.
3636
/// - In the context of SSH key encryption, AAD will be empty.
3737
///
38+
/// ## Implementing packet encryption
39+
/// The [`ChaCha20Poly1305`] type implements *just* the AEAD primitive used by OpenSSH, and takes
40+
/// a 32-byte/256-bit key. However, the full construction used for packet encryption requires
41+
/// 64-byte/512-bit keys, which are split into two 32-byte/256-bit keys (`K_1` and `K_2`), where
42+
/// `K_1` is used for length encryption, and `K_2` for the packet body in addition to authenticating
43+
/// the ciphertext encrypted under `K_1`.
44+
///
45+
/// ## Packet encryption example
46+
///
47+
/// ```
48+
/// use ssh_cipher::{
49+
/// ChaCha20, ChaCha20Poly1305, ChaChaKey, ChaChaNonce, Tag,
50+
/// aead::{AeadInOut, KeyInit},
51+
/// cipher::{KeyIvInit, StreamCipher},
52+
/// };
53+
///
54+
/// fn encrypt_packet(
55+
/// key: &[u8; 64],
56+
/// seq_num: u32,
57+
/// packet: &mut [u8],
58+
/// ) -> Result<([u8; 4], Tag), aead::Error> {
59+
/// let (k1, k2) = split_key(key);
60+
/// let mut nonce = ChaChaNonce::default();
61+
/// nonce[4..].copy_from_slice(&seq_num.to_be_bytes());
62+
///
63+
/// // Encrypt 4-byte packet length under `K_1` using raw `ChaCha20`.
64+
/// let packet_len = u32::try_from(packet.len()).map_err(|_| aead::Error)?;
65+
/// let mut len_bytes = packet_len.to_be_bytes();
66+
/// let mut len_cipher = ChaCha20::new(&k1, &nonce);
67+
/// len_cipher.apply_keystream(&mut len_bytes);
68+
///
69+
/// // Encrypt packet body while authenticating encrypted `len_ct` as AAD.
70+
/// let body_cipher = ChaCha20Poly1305::new(&k2);
71+
/// let tag = body_cipher.encrypt_inout_detached(&nonce, &len_bytes, packet.into())?;
72+
/// Ok((len_bytes, tag))
73+
/// }
74+
///
75+
/// fn decrypt_packet(
76+
/// key: &[u8; 64],
77+
/// seq_num: u32,
78+
/// mut enc_len: [u8; 4],
79+
/// packet: &mut [u8],
80+
/// tag: &Tag,
81+
/// ) -> Result<u32, aead::Error> {
82+
/// let (k1, k2) = split_key(key);
83+
/// let mut nonce = ChaChaNonce::default();
84+
/// nonce[4..].copy_from_slice(&seq_num.to_be_bytes());
85+
///
86+
/// // Authenticate length and decrypt body
87+
/// let aead = ChaCha20Poly1305::new(&k2);
88+
/// aead.decrypt_inout_detached(&nonce, &enc_len, packet.into(), tag)?;
89+
///
90+
/// // If ciphertext is authentic, decrypt the length
91+
/// let mut len_cipher = ChaCha20::new(&k2, &nonce);
92+
/// len_cipher.apply_keystream(&mut enc_len);
93+
/// Ok(u32::from_be_bytes(enc_len))
94+
/// }
95+
///
96+
/// // Split 512-bit key into `K_1` and `K_2`
97+
/// fn split_key(key: &[u8; 64]) -> (ChaChaKey, ChaChaKey) {
98+
/// let (k1, k2) = key.split_at(32);
99+
/// (ChaChaKey::try_from(k1).unwrap(), ChaChaKey::try_from(k2).unwrap())
100+
/// }
101+
/// ```
102+
///
38103
/// [PROTOCOL.chacha20poly1305]: https://web.mit.edu/freebsd/head/crypto/openssh/PROTOCOL.chacha20poly1305
39104
/// [RFC8439]: https://datatracker.ietf.org/doc/html/rfc8439
40105
#[derive(Clone)]

ssh-cipher/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ pub use cipher;
1818

1919
#[cfg(feature = "chacha20poly1305")]
2020
pub use crate::chacha20poly1305::{ChaCha20, ChaCha20Poly1305, ChaChaKey, ChaChaNonce};
21+
#[cfg(any(feature = "aes", feature = "chacha20poly1305"))]
22+
pub use aead;
2123

2224
use cipher::array::{Array, typenum::U16};
2325
use core::{fmt, str};

0 commit comments

Comments
 (0)