encrypted_message/
strategy.rs

1//! All the encryption strategies that can be used with [`EncryptedMessage`](crate::EncryptedMessage).
2
3use std::fmt::Debug;
4
5use hmac::{Hmac, Mac};
6use sha2::Sha256;
7
8mod private {
9    pub trait Sealed {}
10
11    impl Sealed for super::Deterministic {}
12    impl Sealed for super::Randomized {}
13}
14
15pub trait Strategy: private::Sealed + Debug {
16    /// Generates a 96-bit nonce to encrypt a payload.
17    fn generate_nonce_for(payload: &[u8], key: &[u8; 32]) -> [u8; 12];
18}
19
20/// This encryption strategy is guaranteed to always produce the same nonce for a payload,
21/// which will generate the same encrypted message every time.
22///
23/// This is useful for data you'd like to be able to query, as you can simply encrypt
24/// the payload you're querying for & search for the same encrypted message.
25#[derive(Debug, PartialEq, Eq)]
26pub struct Deterministic;
27impl Strategy for Deterministic {
28    /// Generates a deterministic 96-bit nonce for the payload.
29    fn generate_nonce_for(payload: &[u8], key: &[u8; 32]) -> [u8; 12] {
30        let mut mac = Hmac::<Sha256>::new_from_slice(key).unwrap();
31        mac.update(payload);
32
33        mac.finalize().into_bytes()[0..12].try_into().unwrap()
34    }
35}
36
37/// This encryption strategy is guaranteed to always produce a random nonce, regardless of the payload,
38/// which will generate a different encrypted message every time.
39///
40/// This encryption strategy improves security by making crypto-analysis of encrypted messages harder,
41/// but makes querying them without decrypting all data impossible.
42#[derive(Debug, PartialEq, Eq)]
43pub struct Randomized;
44impl Strategy for Randomized {
45    /// Generates a random 96-bit nonce for the payload.
46    fn generate_nonce_for(_payload: &[u8], _key: &[u8; 32]) -> [u8; 12] {
47        rand::random()
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    use secrecy::ExposeSecret as _;
56
57    use crate::{
58        config::Config as _,
59        testing::{TestConfigDeterministic, TestConfigRandomized},
60        utilities::base64,
61    };
62
63    mod deterministic {
64        use super::*;
65
66        #[test]
67        fn nonce_is_deterministic() {
68            let key = TestConfigDeterministic.primary_key();
69            let nonce = Deterministic::generate_nonce_for("rigo is cool".as_bytes(), key.expose_secret());
70
71            // Test that the nonce is 12 bytes long.
72            assert_eq!(nonce.len(), 12);
73
74            // Test that the nonce is deterministic.
75            assert_eq!(nonce, *base64::decode("Ts2jGkMEW9NFsQZX").unwrap());
76        }
77    }
78
79    mod randomized {
80        use super::*;
81
82        #[test]
83        fn nonce_is_randomized() {
84            let payload = "much secret much secure".as_bytes();
85            let key = TestConfigRandomized.primary_key();
86            let first_nonce = Randomized::generate_nonce_for(payload, key.expose_secret());
87            let second_nonce = Randomized::generate_nonce_for(payload, key.expose_secret());
88
89            // Test that the nonces are 12 bytes long.
90            assert_eq!(first_nonce.len(), 12);
91            assert_eq!(second_nonce.len(), 12);
92
93            // Test that the nonces never match, even when generated for the same payload.
94            assert_ne!(first_nonce, second_nonce);
95        }
96    }
97}