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