1mod domain;
2mod error;
3mod fiestal;
4mod prf;
5
6pub use error::Error as FieldEncryptionError;
7
8const ROUNDS: usize = 7;
9
10pub 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 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 top_bits += 1;
55 }
56
57 Ok(Self {
58 input,
59 output,
60 fiestal,
61 trim_bytes,
62 top_bits,
63 })
64 }
65
66 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 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}