field_encryption/
lib.rs

1mod domain;
2mod error;
3mod fiestal;
4mod prf;
5
6pub use error::Error as FieldEncryptionError;
7
8const ROUNDS: usize = 7;
9
10/// `FieldEncryption` struct. Provides methods to encrypt values matching the input regex to a
11/// value matching the output regex (and to decrypt).
12///
13/// Both regular expressions must be finite, i.e. they must contain a bounded number of possible
14/// values - so no unbounded expressions such as `.*` or `[0-9]+` are permitted.
15///
16/// Furthermore the number of possible values of the output regex must be equal to, or greater than,
17/// the number of possible values of the input regex, i.e. the 'domain' of the output regex must be
18/// greater than or equal to the 'domain' pf the input regex.
19pub struct FieldEncryption {
20    input: domain::RegexDomain,
21    output: domain::RegexDomain,
22    fiestal: fiestal::Fiestal,
23    trim_bytes: usize,
24    top_bits: u32,
25}
26
27impl FieldEncryption {
28    /// Create a new `FieldEncryption` instance.
29    /// # Arguments
30    /// * `input_regex` - the regular expression that describes all inputs
31    /// * `output_regex` - the regular expression that describes all outputs
32    /// * `key` - the encryption key, must be at least 32 bytes
33    ///
34    /// # Errors
35    /// * `InfiniteRegex` - if either regex is infinite
36    /// * `OutputDomainTooSmall` - if the domain of the output regex is < domain of the input regex
37    /// * `InvalidKeyLength` - of the encryption key is too small
38    pub fn new(input_regex: &str, output_regex: &str, key: &[u8]) -> Result<Self, error::Error> {
39        let input = domain::RegexDomain::new(input_regex)?;
40        let output = domain::RegexDomain::new(output_regex)?;
41
42        if input.len() > output.len() {
43            return Err(error::Error::OutputDomainTooSmall);
44        }
45        let prfs = prf::prfs(key, ROUNDS)?;
46        let fiestal = fiestal::Fiestal::new(prfs)?;
47
48        let output_max = output.len();
49        let zero_bits = output_max.leading_zeros();
50        let trim_bytes = (zero_bits / 8) as usize;
51        let mut top_bits = 8 - (zero_bits % 8);
52        if top_bits % 2 == 1 {
53            // must have even number bits
54            top_bits += 1;
55        }
56
57        Ok(Self {
58            input,
59            output,
60            fiestal,
61            trim_bytes,
62            top_bits,
63        })
64    }
65
66    /// Encrypts the supplied input.
67    /// # Errors
68    /// * `InvalidInput` - if the specified value does not match the input regex
69    pub fn encrypt(&self, input: &str) -> Result<String, error::Error> {
70        self.execute(input, &self.input, &self.output, |data, bits| {
71            self.fiestal.encrypt(data, bits);
72        })
73    }
74
75    /// Decrypts the supplied input.
76    /// # Errors
77    /// * `InvalidInput` - if the specified value does not match the output regex
78    pub fn decrypt(&self, input: &str) -> Result<String, error::Error> {
79        self.execute(input, &self.output, &self.input, |data, bits| {
80            self.fiestal.decrypt(data, bits);
81        })
82    }
83
84    fn execute(
85        &self,
86        input: &str,
87        from: &domain::RegexDomain,
88        to: &domain::RegexDomain,
89        func: impl Fn(&mut [u8], u32),
90    ) -> Result<String, error::Error> {
91        let input_offset = from
92            .offset(input.as_bytes())
93            .ok_or_else(|| error::Error::InvalidInput(input.to_owned()))?;
94
95        let mut input_bytes = input_offset.to_be_bytes();
96        let mut output_offset = self.output.len();
97        while output_offset >= self.output.len() {
98            func(&mut input_bytes[self.trim_bytes..], self.top_bits);
99            output_offset = u128::from_be_bytes(input_bytes);
100        }
101        let output_bytes = to
102            .nth(output_offset)
103            .ok_or_else(|| error::Error::InvalidOutputOffset(output_offset))?;
104
105        Ok(String::from_utf8(output_bytes)?)
106    }
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112
113    #[test]
114    fn round_trip() {
115        let input = "11211";
116        let fe = FieldEncryption::new(r"[0-9]{1,9}", r"[a-z]{1,17}", &[23; 32]).unwrap();
117        let cipher = fe.encrypt(input).unwrap();
118        println!("cipher = {}", cipher);
119        let plain = fe.decrypt(&cipher).unwrap();
120        println!("plain = {}", plain);
121        assert_eq!(input, plain.as_str());
122    }
123
124    #[test]
125    fn readme() {
126        let fe = FieldEncryption::new(
127            r"[A-Z][a-z]{1,4} [A-Z][a-z]{1,4}!",
128            r"[a-z]{5} [a-z]{7}",
129            &[0; 32],
130        )
131        .unwrap();
132        let cipher_text = fe.encrypt("Hello World!").unwrap();
133        println!("{}", cipher_text);
134        let plain_text = fe.decrypt(&cipher_text).unwrap();
135        println!("{}", plain_text);
136    }
137}