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::CryptoRngCore, 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(algorithm: KdfAlg, rng: &mut impl CryptoRngCore) -> Result<Self> {
44        let mut salt = vec![0u8; DEFAULT_SALT_SIZE];
45        rng.fill_bytes(&mut salt);
46
47        match algorithm {
48            KdfAlg::None => {
49                // Disallow explicit initialization with a `none` algorithm
50                Err(Error::AlgorithmUnknown)
51            }
52            KdfAlg::Bcrypt => Ok(Kdf::Bcrypt {
53                salt,
54                rounds: DEFAULT_BCRYPT_ROUNDS,
55            }),
56        }
57    }
58
59    /// Get the KDF algorithm.
60    pub fn algorithm(&self) -> KdfAlg {
61        match self {
62            Self::None => KdfAlg::None,
63            #[cfg(feature = "alloc")]
64            Self::Bcrypt { .. } => KdfAlg::Bcrypt,
65        }
66    }
67
68    /// Derive an encryption key from the given password.
69    #[cfg(feature = "encryption")]
70    pub fn derive(&self, password: impl AsRef<[u8]>, output: &mut [u8]) -> Result<()> {
71        match self {
72            Kdf::None => Err(Error::Decrypted),
73            Kdf::Bcrypt { salt, rounds } => {
74                bcrypt_pbkdf(password, salt, *rounds, output).map_err(|_| Error::Crypto)?;
75                Ok(())
76            }
77        }
78    }
79
80    /// Derive key and IV for the given [`Cipher`].
81    ///
82    /// Returns two byte vectors containing the key and IV respectively.
83    #[cfg(feature = "encryption")]
84    pub fn derive_key_and_iv(
85        &self,
86        cipher: Cipher,
87        password: impl AsRef<[u8]>,
88    ) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
89        let (key_size, iv_size) = cipher.key_and_iv_size().ok_or(Error::Decrypted)?;
90        let okm_size = key_size
91            .checked_add(iv_size)
92            .ok_or(encoding::Error::Length)?;
93
94        let mut okm = Zeroizing::new(vec![0u8; okm_size]);
95        self.derive(password, &mut okm)?;
96        let iv = okm.split_off(key_size);
97        Ok((okm, iv))
98    }
99
100    /// Is the KDF configured as `none`?
101    pub fn is_none(&self) -> bool {
102        self == &Self::None
103    }
104
105    /// Is the KDF configured as anything other than `none`?
106    pub fn is_some(&self) -> bool {
107        !self.is_none()
108    }
109
110    /// Is the KDF configured as `bcrypt` (i.e. bcrypt-pbkdf)?
111    #[cfg(feature = "alloc")]
112    pub fn is_bcrypt(&self) -> bool {
113        matches!(self, Self::Bcrypt { .. })
114    }
115}
116
117impl Default for Kdf {
118    fn default() -> Self {
119        Self::None
120    }
121}
122
123impl Decode for Kdf {
124    type Error = Error;
125
126    fn decode(reader: &mut impl Reader) -> Result<Self> {
127        match KdfAlg::decode(reader)? {
128            KdfAlg::None => {
129                if usize::decode(reader)? == 0 {
130                    Ok(Self::None)
131                } else {
132                    Err(Error::AlgorithmUnknown)
133                }
134            }
135            KdfAlg::Bcrypt => {
136                #[cfg(not(feature = "alloc"))]
137                return Err(Error::AlgorithmUnknown);
138
139                #[cfg(feature = "alloc")]
140                reader.read_prefixed(|reader| {
141                    Ok(Self::Bcrypt {
142                        salt: Vec::decode(reader)?,
143                        rounds: u32::decode(reader)?,
144                    })
145                })
146            }
147        }
148    }
149}
150
151impl Encode for Kdf {
152    fn encoded_len(&self) -> encoding::Result<usize> {
153        let kdfopts_prefixed_len = match self {
154            Self::None => 4,
155            #[cfg(feature = "alloc")]
156            Self::Bcrypt { salt, .. } => [12, salt.len()].checked_sum()?,
157        };
158
159        [self.algorithm().encoded_len()?, kdfopts_prefixed_len].checked_sum()
160    }
161
162    fn encode(&self, writer: &mut impl Writer) -> encoding::Result<()> {
163        self.algorithm().encode(writer)?;
164
165        match self {
166            Self::None => 0usize.encode(writer)?,
167            #[cfg(feature = "alloc")]
168            Self::Bcrypt { salt, rounds } => {
169                [8, salt.len()].checked_sum()?.encode(writer)?;
170                salt.encode(writer)?;
171                rounds.encode(writer)?
172            }
173        }
174
175        Ok(())
176    }
177}