cipher_crypt/
affine.rs

1//! The Affine cipher is a special case of the more general monoalphabetic substitution cipher.
2//!
3//! The cipher is less secure than a substitution cipher as it is vulnerable to all of the attacks
4//! that work against substitution ciphers, in addition to other attacks. The cipher's primary
5//! weakness comes from the fact that if the cryptanalyst can discover the plaintext of two
6//! ciphertext characters, then the key can be obtained by solving a simultaneous equation
7//!
8use crate::common::alphabet::Alphabet;
9use crate::common::cipher::Cipher;
10use crate::common::{alphabet, substitute};
11use num::integer::gcd;
12
13/// An Affine cipher.
14///
15/// This struct is created by the `new()` method. See its documentation for more.
16pub struct Affine {
17    a: usize,
18    b: usize,
19}
20
21impl Cipher for Affine {
22    type Key = (usize, usize);
23    type Algorithm = Affine;
24
25    /// Initialise an Affine cipher given the key (`a`, `b`).
26    ///
27    /// # Panics
28    /// * `a` or `b` are not in the inclusive range `1 - 26`.
29    /// * `a` has a factor in common with 26.
30    ///
31    fn new(key: (usize, usize)) -> Affine {
32        let (a, b) = key;
33        if (a < 1 || b < 1) || (a > 26 || b > 26) {
34            panic!("The keys a & b must be within the range 1 <= n <= 26.");
35        }
36
37        if gcd(a, 26) > 1 {
38            panic!("The key 'a' cannot share a common factor with 26.");
39        }
40
41        Affine { a, b }
42    }
43
44    /// Encrypt a message using an Affine cipher.
45    ///
46    /// # Examples
47    /// Basic usage:
48    ///
49    /// ```
50    /// use cipher_crypt::{Cipher, Affine};
51    ///
52    /// let a = Affine::new((3, 7));
53    /// assert_eq!("Hmmhnl hm qhvu!", a.encrypt("Attack at dawn!").unwrap());
54    /// ```
55    ///
56    fn encrypt(&self, message: &str) -> Result<String, &'static str> {
57        // Encryption of a letter:
58        //         E(x) = (ax + b) mod 26
59        // Where;  x    = position of letter in alphabet
60        //         a, b = the numbers of the affine key
61        Ok(substitute::shift_substitution(message, |idx| {
62            alphabet::STANDARD.modulo(((self.a * idx) + self.b) as isize)
63        }))
64    }
65
66    /// Decrypt a message using an Affine cipher.
67    ///
68    /// # Examples
69    /// Basic usage:
70    ///
71    /// ```
72    /// use cipher_crypt::{Cipher, Affine};
73    ///
74    /// let a = Affine::new((3, 7));
75    /// assert_eq!("Attack at dawn!", a.decrypt("Hmmhnl hm qhvu!").unwrap());
76    /// ```
77    ///
78    fn decrypt(&self, ciphertext: &str) -> Result<String, &'static str> {
79        // Decryption of a letter:
80        //         D(x) = (a^-1*(x - b)) mod 26
81        // Where;  x    = position of letter in alphabet
82        //         a^-1 = multiplicative inverse of the key number `a`
83        //         b    = a number of the affine key
84        let a_inv = alphabet::STANDARD
85            .multiplicative_inverse(self.a as isize)
86            .expect("Multiplicative inverse for 'a' could not be calculated.");
87
88        Ok(substitute::shift_substitution(ciphertext, |idx| {
89            alphabet::STANDARD.modulo(a_inv as isize * (idx as isize - self.b as isize))
90        }))
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn encrypt_message() {
100        let a = Affine::new((3, 7));
101        assert_eq!("Hmmhnl hm qhvu!", a.encrypt("Attack at dawn!").unwrap());
102    }
103
104    #[test]
105    fn decrypt_message() {
106        let a = Affine::new((3, 7));
107        assert_eq!("Attack at dawn!", a.decrypt("Hmmhnl hm qhvu!").unwrap());
108    }
109
110    #[test]
111    fn with_utf8() {
112        let a = Affine::new((15, 10));
113        let message = "Peace ✌️ Freedom and Liberty!";
114
115        assert_eq!(message, a.decrypt(&a.encrypt(message).unwrap()).unwrap());
116    }
117
118    #[test]
119    fn exhaustive_encrypt() {
120        //Test with every combination of a and b
121        let message = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
122
123        for a in 1..27 {
124            if gcd(a, 26) > 1 {
125                continue;
126            }
127
128            for b in 1..27 {
129                let a = Affine::new((a, b));
130                assert_eq!(message, a.decrypt(&a.encrypt(message).unwrap()).unwrap());
131            }
132        }
133    }
134
135    #[test]
136    fn valid_key() {
137        Affine::new((15, 17));
138    }
139
140    #[test]
141    fn b_shares_factor() {
142        Affine::new((15, 2));
143    }
144
145    #[test]
146    #[should_panic]
147    fn a_shares_factor() {
148        Affine::new((2, 15));
149    }
150
151    #[test]
152    #[should_panic]
153    fn keys_to_small() {
154        Affine::new((0, 10));
155    }
156
157    #[test]
158    #[should_panic]
159    fn keys_to_big() {
160        Affine::new((30, 51));
161    }
162}