ssh_key/
kdf.rs

1//! Key Derivation Functions.
2//!
3//! These are used for deriving an encryption key from a password.
4
5use crate::{Error, KdfAlg, Result};
6use encoding::{CheckedSum, Decode, Encode, Reader, Writer};
7
8#[cfg(feature = "alloc")]
9use alloc::vec::Vec;
10
11#[cfg(feature = "encryption")]
12use {crate::Cipher, bcrypt_pbkdf::bcrypt_pbkdf, rand_core::TryCryptoRng, zeroize::Zeroizing};
13
14/// Default number of rounds to use for bcrypt-pbkdf.
15#[cfg(feature = "encryption")]
16const DEFAULT_BCRYPT_ROUNDS: u32 = 16;
17
18/// Default salt size. Matches OpenSSH.
19#[cfg(feature = "encryption")]
20const DEFAULT_SALT_SIZE: usize = 16;
21
22/// Key Derivation Functions (KDF).
23#[derive(Clone, Debug, Eq, PartialEq)]
24#[non_exhaustive]
25pub enum Kdf {
26    /// No KDF.
27    None,
28
29    /// bcrypt-pbkdf options.
30    #[cfg(feature = "alloc")]
31    Bcrypt {
32        /// Salt
33        salt: Vec<u8>,
34
35        /// Rounds
36        rounds: u32,
37    },
38}
39
40impl Kdf {
41    /// Initialize KDF configuration for the given algorithm.
42    #[cfg(feature = "encryption")]
43    pub fn new<R: TryCryptoRng + ?Sized>(algorithm: KdfAlg, rng: &mut R) -> Result<Self> {
44        let mut salt = vec![0u8; DEFAULT_SALT_SIZE];
45        rng.try_fill_bytes(&mut salt)
46            .map_err(|_| Error::RngFailure)?;
47
48        match algorithm {
49            KdfAlg::None => {
50                // Disallow explicit initialization with a `none` algorithm
51                Err(Error::AlgorithmUnknown)
52            }
53            KdfAlg::Bcrypt => Ok(Kdf::Bcrypt {
54                salt,
55                rounds: DEFAULT_BCRYPT_ROUNDS,
56            }),
57        }
58    }
59
60    /// Get the KDF algorithm.
61    pub fn algorithm(&self) -> KdfAlg {
62        match self {
63            Self::None => KdfAlg::None,
64            #[cfg(feature = "alloc")]
65            Self::Bcrypt { .. } => KdfAlg::Bcrypt,
66        }
67    }
68
69    /// Derive an encryption key from the given password.
70    #[cfg(feature = "encryption")]
71    pub fn derive(&self, password: impl AsRef<[u8]>, output: &mut [u8]) -> Result<()> {
72        match self {
73            Kdf::None => Err(Error::Decrypted),
74            Kdf::Bcrypt { salt, rounds } => {
75                bcrypt_pbkdf(password, salt, *rounds, output).map_err(|_| Error::Crypto)?;
76                Ok(())
77            }
78        }
79    }
80
81    /// Derive key and IV for the given [`Cipher`].
82    ///
83    /// Returns two byte vectors containing the key and IV respectively.
84    #[cfg(feature = "encryption")]
85    pub fn derive_key_and_iv(
86        &self,
87        cipher: Cipher,
88        password: impl AsRef<[u8]>,
89    ) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
90        let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?;
91
92        let okm_size = key_size
93            .checked_add(iv_size)
94            .ok_or(encoding::Error::Length)?;
95
96        let mut okm = Zeroizing::new(vec![0u8; okm_size]);
97        self.derive(password, &mut okm)?;
98        let mut iv = okm.split_off(key_size);
99
100        // For whatever reason `chacha20-poly1305@openssh.com` uses a nonce of all zeros for private
101        // key encryption, relying on a unique salt used in the password-based encryption key
102        // derivation to ensure that each encryption key is only used once.
103        if cipher == Cipher::ChaCha20Poly1305 {
104            iv.copy_from_slice(&cipher::ChaChaNonce::default());
105        }
106
107        Ok((okm, iv))
108    }
109
110    /// Is the KDF configured as `none`?
111    pub fn is_none(&self) -> bool {
112        self == &Self::None
113    }
114
115    /// Is the KDF configured as anything other than `none`?
116    pub fn is_some(&self) -> bool {
117        !self.is_none()
118    }
119
120    /// Is the KDF configured as `bcrypt` (i.e. bcrypt-pbkdf)?
121    #[cfg(feature = "alloc")]
122    pub fn is_bcrypt(&self) -> bool {
123        matches!(self, Self::Bcrypt { .. })
124    }
125}
126
127impl Default for Kdf {
128    fn default() -> Self {
129        Self::None
130    }
131}
132
133impl Decode for Kdf {
134    type Error = Error;
135
136    fn decode(reader: &mut impl Reader) -> Result<Self> {
137        match KdfAlg::decode(reader)? {
138            KdfAlg::None => {
139                if usize::decode(reader)? == 0 {
140                    Ok(Self::None)
141                } else {
142                    Err(Error::AlgorithmUnknown)
143                }
144            }
145            KdfAlg::Bcrypt => {
146                #[cfg(not(feature = "alloc"))]
147                return Err(Error::AlgorithmUnknown);
148
149                #[cfg(feature = "alloc")]
150                reader.read_prefixed(|reader| {
151                    Ok(Self::Bcrypt {
152                        salt: Vec::decode(reader)?,
153                        rounds: u32::decode(reader)?,
154                    })
155                })
156            }
157        }
158    }
159}
160
161impl Encode for Kdf {
162    fn encoded_len(&self) -> encoding::Result<usize> {
163        let kdfopts_prefixed_len = match self {
164            Self::None => 4,
165            #[cfg(feature = "alloc")]
166            Self::Bcrypt { salt, .. } => [12, salt.len()].checked_sum()?,
167        };
168
169        [self.algorithm().encoded_len()?, kdfopts_prefixed_len].checked_sum()
170    }
171
172    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
173        self.algorithm().encode(writer)?;
174
175        match self {
176            Self::None => 0usize.encode(writer)?,
177            #[cfg(feature = "alloc")]
178            Self::Bcrypt { salt, rounds } => {
179                [8, salt.len()].checked_sum()?.encode(writer)?;
180                salt.encode(writer)?;
181                rounds.encode(writer)?
182            }
183        }
184
185        Ok(())
186    }
187}