ssh_key/
kdf.rs

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