Skip to main content

devolutions_crypto/
utils.rs

1//! Module for utils that does not use any of the Devolutions custom data types.
2
3use base64::{
4    alphabet,
5    engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig},
6    Engine as _,
7};
8use hmac::Hmac;
9use pbkdf2::pbkdf2;
10use rand::TryRng;
11use sha2::Sha256;
12use subtle::ConstantTimeEq as _;
13
14use crate::online_ciphertext::OnlineCiphertextHeader;
15
16use super::Argon2Parameters;
17use super::DataType;
18use super::Error;
19use super::Header;
20use super::Result;
21
22/// Returns a random key of the specified length. Can also be used
23///  whenever you need a random byte array, like for a salt.
24/// # Arguments
25///  * `length` - Length of the desired key.
26/// # Example
27/// ```
28/// use devolutions_crypto::utils::generate_key;
29///
30/// let key = generate_key(32).expect("generate key shouldn't fail");;
31/// assert_eq!(32, key.len());
32/// ```
33pub fn generate_key(length: usize) -> Result<Vec<u8>> {
34    let mut key = vec![0u8; length];
35    rand::rngs::SysRng
36        .try_fill_bytes(&mut key)
37        .map_err(|_| Error::RandomError)?;
38    Ok(key)
39}
40
41/// Derives a password or key into a new one using PBKDF2.
42/// # Arguments
43///  * `key` - The key or password to derive.
44///  * `salt` - The cryptographic salt to be used to add randomness. Can be empty. Recommended size is 16 bytes.
45///  * `iterations` - The number of time the key will be derived. A higher number is slower but harder to brute-force.
46///    600,000 iterations are recommended for a password.
47///  * `length` - Length of the desired key.
48/// # Example
49/// ```
50/// use devolutions_crypto::utils::{derive_key_pbkdf2, generate_key};
51/// let key = b"this is a secret password";
52/// let salt = generate_key(16).expect("generate key shouldn't fail");;
53/// let iterations = 600000;
54/// let length = 32;
55///
56/// let new_key = derive_key_pbkdf2(key, &salt, iterations, length);
57///
58/// assert_eq!(32, new_key.len());
59/// ```
60pub fn derive_key_pbkdf2(key: &[u8], salt: &[u8], iterations: u32, length: usize) -> Vec<u8> {
61    let mut new_key = vec![0u8; length];
62    let _ = pbkdf2::<Hmac<Sha256>>(key, salt, iterations, &mut new_key);
63    new_key
64}
65
66/// Derives a password or key into a new one using Argon2.
67/// # Arguments
68///  * `key` - The key or password to derive.
69///  * `parameters` - The `Argon2Parameters` to use.
70/// # Example
71/// ```
72/// use devolutions_crypto::utils::{derive_key_argon2, generate_key};
73/// use devolutions_crypto::Argon2Parameters;
74/// let key = b"this is a secret password";
75/// let parameters = Argon2Parameters::default();
76///
77/// let new_key = derive_key_argon2(key, &parameters).expect("default parameters should not fail");
78///
79/// assert_eq!(32, new_key.len());
80/// ```
81pub fn derive_key_argon2(key: &[u8], parameters: &Argon2Parameters) -> Result<Vec<u8>> {
82    parameters.compute(key)
83}
84
85/// Only validate the header to make sure it is valid. Used to quickly determine if the data comes from the library.
86/// # Arguments
87///  * `data` - The data to verify.
88///  * `data_type` - The type of the data.
89/// # Returns
90/// `true` if the header is valid, `false` if it is not.
91/// # Example
92/// use devolutions_crypto::DataType;
93/// use devolutions_crypto::ciphertext::{encrypt, CiphertextVersion};
94/// use devolutions_crypto::utils::{generate_key, validate_header};
95///
96/// let key = generate_key(32);
97/// let ciphertext: `Vec<u8>` = encrypt(b"test", &key, CiphertextVersion::Latest).unwrap().into();
98///
99/// assert!(validate_header(&ciphertext, DataType::Ciphertext);
100/// assert!(!validate_header(&ciphertext, DataType::PasswordHash);
101/// assert!(!validate_header(&key, DataType::Ciphertext);
102pub fn validate_header(data: &[u8], data_type: DataType) -> bool {
103    use super::ciphertext::Ciphertext;
104    use super::derive_encrypt::KdfEncryptedData;
105    use super::key::{PrivateKey, PublicKey};
106    use super::password_hash::PasswordHash;
107    use super::secret_sharing::Share;
108    use super::signature::Signature;
109    use super::signing_key::{SigningKeyPair, SigningPublicKey};
110
111    if data.len() < Header::len() {
112        return false;
113    }
114
115    match data_type {
116        DataType::None => false,
117        DataType::Ciphertext => Header::<Ciphertext>::try_from(&data[0..Header::len()]).is_ok(),
118        DataType::PasswordHash => Header::<PasswordHash>::try_from(&data[0..Header::len()]).is_ok(),
119        DataType::Key => {
120            Header::<PrivateKey>::try_from(&data[0..Header::len()]).is_ok()
121                || Header::<PublicKey>::try_from(&data[0..Header::len()]).is_ok()
122        }
123        DataType::SigningKey => {
124            Header::<SigningKeyPair>::try_from(&data[0..Header::len()]).is_ok()
125                || Header::<SigningPublicKey>::try_from(&data[0..Header::len()]).is_ok()
126        }
127        DataType::Share => Header::<Share>::try_from(&data[0..Header::len()]).is_ok(),
128        DataType::Signature => Header::<Signature>::try_from(&data[0..Header::len()]).is_ok(),
129        DataType::OnlineCiphertext => {
130            Header::<OnlineCiphertextHeader>::try_from(&data[0..Header::len()]).is_ok()
131        }
132        DataType::KeyDerivation => {
133            use super::key_derivation::DerivationParameters;
134            Header::<DerivationParameters>::try_from(&data[0..Header::len()]).is_ok()
135        }
136        DataType::KdfEncryptedData => {
137            Header::<KdfEncryptedData>::try_from(&data[0..Header::len()]).is_ok()
138        }
139    }
140}
141
142/// Temporarly binded here for a specific use case, don't rely on this.
143///
144/// Copied and modified from:
145/// <https://github.com/RustCrypto/password-hashing/blob/master/scrypt/src/simple.rs>
146/// Because rand is outdated, I cannot use the crate directly
147pub fn scrypt_simple(password: &[u8], salt: &[u8], log_n: u8, r: u32, p: u32) -> String {
148    use byteorder::{ByteOrder, LittleEndian};
149
150    let params = scrypt::Params::new(log_n, r, p).expect("params should be valid");
151
152    // 256-bit derived key
153    let mut dk = [0u8; 32];
154
155    scrypt::scrypt(password, salt, &params, &mut dk)
156        .expect("32 bytes always satisfy output length requirements");
157
158    // usually 128 bytes is enough
159    let mut result = String::with_capacity(128);
160    result.push_str("$rscrypt$");
161    if r < 256 && p < 256 {
162        result.push_str("0$");
163        let mut tmp = [0u8; 3];
164        tmp[0] = log_n;
165        tmp[1] = r as u8;
166        tmp[2] = p as u8;
167        result.push_str(&DEVO_BASE64.encode(tmp));
168    } else {
169        result.push_str("1$");
170        let mut tmp = [0u8; 9];
171        tmp[0] = log_n;
172        LittleEndian::write_u32(&mut tmp[1..5], r);
173        LittleEndian::write_u32(&mut tmp[5..9], p);
174        result.push_str(&DEVO_BASE64.encode(tmp));
175    }
176    result.push('$');
177    result.push_str(&DEVO_BASE64.encode(salt));
178    result.push('$');
179    result.push_str(&DEVO_BASE64.encode(dk));
180    result.push('$');
181
182    result
183}
184
185pub fn base64_encode(data: &[u8]) -> String {
186    DEVO_BASE64.encode(data)
187}
188
189pub fn base64_encode_url(data: &[u8]) -> String {
190    DEVO_BASE64_URLSAFE_NOPAD.encode(data)
191}
192
193pub fn base64_decode(data: &str) -> Result<Vec<u8>> {
194    match DEVO_BASE64.decode(data) {
195        Ok(d) => Ok(d),
196        _ => Err(Error::InvalidData),
197    }
198}
199
200pub fn base64_decode_url(data: &str) -> Result<Vec<u8>> {
201    match DEVO_BASE64_URLSAFE_NOPAD.decode(data) {
202        Ok(d) => Ok(d),
203        _ => Err(Error::InvalidData),
204    }
205}
206
207pub fn constant_time_equals(x: &[u8], y: &[u8]) -> bool {
208    x.ct_eq(y).into()
209}
210
211const BASE64_CONFIG: GeneralPurposeConfig = GeneralPurposeConfig::new()
212    .with_encode_padding(true)
213    .with_decode_padding_mode(DecodePaddingMode::Indifferent)
214    .with_decode_allow_trailing_bits(true);
215
216const BASE64_CONFIG_NO_PAD: GeneralPurposeConfig = GeneralPurposeConfig::new()
217    .with_encode_padding(false)
218    .with_decode_padding_mode(DecodePaddingMode::Indifferent)
219    .with_decode_allow_trailing_bits(true);
220
221const DEVO_BASE64: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, BASE64_CONFIG);
222const DEVO_BASE64_URLSAFE_NOPAD: GeneralPurpose =
223    GeneralPurpose::new(&alphabet::URL_SAFE, BASE64_CONFIG_NO_PAD);
224
225#[test]
226fn test_constant_time_equals() {
227    let x: [u8; 3] = [0, 1, 2];
228    let y: [u8; 3] = [4, 5, 6];
229    let z: [u8; 4] = [0, 1, 2, 3];
230
231    assert!(constant_time_equals(&x, &x));
232    assert!(!constant_time_equals(&x, &y));
233    assert!(!constant_time_equals(&x, &z));
234    assert!(!constant_time_equals(&y, &x));
235    assert!(constant_time_equals(&y, &y));
236    assert!(!constant_time_equals(&y, &z));
237    assert!(!constant_time_equals(&z, &x));
238    assert!(!constant_time_equals(&z, &y));
239    assert!(constant_time_equals(&z, &z));
240}
241
242#[test]
243fn test_generate_key() {
244    let size = 32;
245    let key = generate_key(size).unwrap();
246
247    assert_eq!(size, key.len());
248    assert_ne!(vec![0u8; size], key);
249}
250
251#[test]
252fn test_derive_key_pbkdf2() {
253    let salt = b"salt";
254    let key = b"key";
255    let iterations = 100;
256    let size = 32;
257
258    let derived = derive_key_pbkdf2(key, salt, iterations, size);
259
260    assert_eq!(size, derived.len());
261    assert_ne!(vec![0u8; size], derived);
262}
263
264#[test]
265fn test_validate_header() {
266    use base64::engine::general_purpose::STANDARD;
267
268    let valid_ciphertext = STANDARD.decode("DQwCAAAAAQA=").unwrap();
269    let valid_password_hash = STANDARD.decode("DQwDAAAAAQA=").unwrap();
270    let valid_share = STANDARD.decode("DQwEAAAAAQA=").unwrap();
271    let valid_private_key = STANDARD.decode("DQwBAAEAAQA=").unwrap();
272    let valid_public_key = STANDARD.decode("DQwBAAEAAQA=").unwrap();
273
274    assert!(validate_header(&valid_ciphertext, DataType::Ciphertext));
275    assert!(validate_header(
276        &valid_password_hash,
277        DataType::PasswordHash
278    ));
279    assert!(validate_header(&valid_share, DataType::Share));
280    assert!(validate_header(&valid_private_key, DataType::Key));
281    assert!(validate_header(&valid_public_key, DataType::Key));
282
283    assert!(!validate_header(&valid_ciphertext, DataType::PasswordHash));
284
285    let invalid_signature = STANDARD.decode("DAwBAAEAAQA=").unwrap();
286    let invalid_type = STANDARD.decode("DQwIAAEAAQA=").unwrap();
287    let invalid_subtype = STANDARD.decode("DQwBAAgAAQA=").unwrap();
288    let invalid_version = STANDARD.decode("DQwBAAEACAA=").unwrap();
289
290    assert!(!validate_header(&invalid_signature, DataType::Key));
291    assert!(!validate_header(&invalid_type, DataType::Key));
292    assert!(!validate_header(&invalid_subtype, DataType::Key));
293    assert!(!validate_header(&invalid_version, DataType::Key));
294
295    let not_long_enough = STANDARD.decode("DQwBAAEAAQ==").unwrap();
296
297    assert!(!validate_header(&not_long_enough, DataType::Key));
298}
299
300#[test]
301fn test_scrypt_simple() {
302    // Regression vector: locks the scrypt output and the `$rscrypt$` format so any
303    // future scrypt crate bump that changes the derived bytes is caught. This value
304    // was verified byte-identical against scrypt 0.11 (the pre-bump version).
305    let hash = scrypt_simple(b"password", b"devolutions-salt", 8, 8, 1);
306
307    assert_eq!(
308        hash,
309        "$rscrypt$0$CAgB$ZGV2b2x1dGlvbnMtc2FsdA==$hIK4WZ1ct+CgcxYxMoi+lkk5RztS/EJBxxm0zmsNVY0=$"
310    );
311}