-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ssh-cipher: make ChaCha20Poly1305 accept a 256-bit key (#254)
In the OpenSSH protocol, ChaCha20Poly1305 uses two different keys: one for length encryption, and one for payloads. It wasn't previously possible to use both keys (or at least quite obtuse) because `ssh-cipher` chopped up `k_1` and `k_2` and only allowed `k_1` to be used. This change hoists that into `ssh-key` for file encryption uses, giving access to the raw `ChaCha20Poly1305` implementation and allowing use of either key. In addition, a nonce is now mandatory, but use of the default all-zero nonce for file encryption purposes is now handled in `ssh-key`.
- Loading branch information
Showing
3 changed files
with
35 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,6 @@ | ||
//! OpenSSH variant of ChaCha20Poly1305: `[email protected]` | ||
//! | ||
//! Differences from ChaCha20Poly1305 as described in RFC8439: | ||
//! | ||
//! - Construction uses two separately keyed instances of ChaCha20: one for data, one for lengths | ||
//! - The input of Poly1305 is not padded | ||
//! - The lengths of ciphertext and AAD are not authenticated using Poly1305 | ||
//! | ||
|
@@ -14,47 +12,18 @@ use cipher::{KeyInit, KeyIvInit, StreamCipher, StreamCipherSeek}; | |
use poly1305::Poly1305; | ||
use subtle::ConstantTimeEq; | ||
|
||
const KEY_SIZE: usize = 32; | ||
|
||
pub(crate) struct ChaCha20Poly1305 { | ||
cipher: ChaCha20, | ||
mac: Poly1305, | ||
} | ||
|
||
impl ChaCha20Poly1305 { | ||
/// Create a new [`ChaCha20Poly1305`] instance with a 64-byte key. | ||
/// From [PROTOCOL.chacha20poly1305]: | ||
/// | ||
/// > The [email protected] cipher requires 512 bits of key | ||
/// > material as output from the SSH key exchange. This forms two 256 bit | ||
/// > keys (K_1 and K_2), used by two separate instances of chacha20. | ||
/// > The first 256 bits constitute K_2 and the second 256 bits become | ||
/// > K_1. | ||
/// > | ||
/// > The instance keyed by K_1 is a stream cipher that is used only | ||
/// > to encrypt the 4 byte packet length field. The second instance, | ||
/// > keyed by K_2, is used in conjunction with poly1305 to build an AEAD | ||
/// > (Authenticated Encryption with Associated Data) that is used to encrypt | ||
/// > and authenticate the entire packet. | ||
/// Create a new [`ChaCha20Poly1305`] instance with a 32-byte key. | ||
/// | ||
/// [PROTOCOL.chacha20poly1305]: https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.chacha20poly1305?annotate=HEAD | ||
pub fn new(key: &[u8], nonce: &[u8]) -> Result<Self> { | ||
#[allow(clippy::arithmetic_side_effects)] | ||
if key.len() != KEY_SIZE * 2 { | ||
return Err(Error::KeySize); | ||
} | ||
|
||
// TODO(tarcieri): support for using both keys | ||
let (k_2, _k_1) = key.split_at(KEY_SIZE); | ||
let key = Key::try_from(k_2).map_err(|_| Error::KeySize)?; | ||
|
||
let nonce = if nonce.is_empty() { | ||
// For key encryption | ||
Nonce::default() | ||
} else { | ||
Nonce::try_from(nonce).map_err(|_| Error::IvSize)? | ||
}; | ||
|
||
let key = Key::try_from(key).map_err(|_| Error::KeySize)?; | ||
let nonce = Nonce::try_from(nonce).map_err(|_| Error::IvSize)?; | ||
let mut cipher = ChaCha20::new(&key, &nonce.into()); | ||
let mut poly1305_key = poly1305::Key::default(); | ||
cipher.apply_keystream(&mut poly1305_key); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -86,14 +86,43 @@ impl Kdf { | |
cipher: Cipher, | ||
password: impl AsRef<[u8]>, | ||
) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> { | ||
let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?; | ||
let (key_size, iv_size) = match cipher { | ||
// Derive two ChaCha20Poly1305 keys, but only use the first. | ||
// In the typical SSH protocol, the second key is used for length encryption. | ||
// | ||
// From `PROTOCOL.chacha20poly1305`: | ||
// | ||
// > The [email protected] cipher requires 512 bits of key | ||
// > material as output from the SSH key exchange. This forms two 256 bit | ||
// > keys (K_1 and K_2), used by two separate instances of chacha20. | ||
// > The first 256 bits constitute K_2 and the second 256 bits become | ||
// > K_1. | ||
// > | ||
// > The instance keyed by K_1 is a stream cipher that is used only | ||
// > to encrypt the 4 byte packet length field. The second instance, | ||
// > keyed by K_2, is used in conjunction with poly1305 to build an AEAD | ||
// > (Authenticated Encryption with Associated Data) that is used to encrypt | ||
// > and authenticate the entire packet. | ||
Cipher::ChaCha20Poly1305 => (64, 0), | ||
_ => cipher.key_and_iv_size().ok_or(Error::Decrypted)?, | ||
}; | ||
|
||
let okm_size = key_size | ||
.checked_add(iv_size) | ||
.ok_or(encoding::Error::Length)?; | ||
|
||
let mut okm = Zeroizing::new(vec![0u8; okm_size]); | ||
self.derive(password, &mut okm)?; | ||
let iv = okm.split_off(key_size); | ||
let mut iv = okm.split_off(key_size); | ||
|
||
if cipher == Cipher::ChaCha20Poly1305 { | ||
// Only use the first ChaCha20 key. | ||
okm.truncate(32); | ||
|
||
// Use an all-zero nonce (with a key derived from password + salt providing uniqueness) | ||
iv.extend_from_slice(&cipher::Nonce::default()); | ||
} | ||
|
||
Ok((okm, iv)) | ||
} | ||
|
||
|