Skip to content

Commit abf6638

Browse files
committed
added hill_cipher for 2x2 keymatrix
1 parent 6493799 commit abf6638

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

src/ciphers/hill.rs

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
use nalgebra::Matrix2;
2+
3+
/// Returns Some(inverse) if it exists (i.e. when gcd(a, m) == 1), else None.
4+
fn mod_inv(a: i32, m: i32) -> Option<i32> {
5+
(1..m).find(|&x| (a * x) % m == 1)
6+
}
7+
8+
fn check_key_validity(key: &Matrix2<i32>) -> Result<(), &'static str> {
9+
let det = key[(0, 0)] * key[(1, 1)] - key[(0, 1)] * key[(1, 0)];
10+
11+
if det.rem_euclid(26) == 0 {
12+
return Err("Error: key matrix is not invertible mod 26");
13+
}
14+
Ok(())
15+
}
16+
17+
fn text_to_numbers(text: &str) -> Vec<u8> {
18+
text.chars()
19+
.filter_map(|c| {
20+
if c.is_ascii_alphabetic() {
21+
Some(c.to_ascii_uppercase() as u8 - b'A')
22+
} else {
23+
None
24+
}
25+
})
26+
.collect()
27+
}
28+
29+
fn numbers_to_text(nums: &[u8]) -> String {
30+
nums.iter()
31+
.map(|&n| ((n.rem_euclid(26)) + b'A') as char)
32+
.collect()
33+
}
34+
35+
fn process_2x2_chunks(text_vector: Vec<u8>, key: &Matrix2<i32>) -> Vec<u8> {
36+
let mut result_data = Vec::new();
37+
38+
for chunk in text_vector.chunks(2) {
39+
// if chunk is incomplete then padding with 0
40+
let a = *chunk.first().unwrap_or(&0) as i32;
41+
let b = *chunk.get(1).unwrap_or(&0) as i32;
42+
43+
// matrix mult
44+
let x = key[(0, 0)] * a + key[(0, 1)] * b;
45+
let y = key[(1, 0)] * a + key[(1, 1)] * b;
46+
result_data.push(x.rem_euclid(26) as u8);
47+
result_data.push(y.rem_euclid(26) as u8);
48+
}
49+
50+
result_data
51+
}
52+
53+
pub fn encode_hill(text: &str, key: Matrix2<i32>) -> Result<String, &'static str> {
54+
check_key_validity(&key)?;
55+
56+
let text_vector = text_to_numbers(text);
57+
let encrypted_data = process_2x2_chunks(text_vector, &key);
58+
Ok(numbers_to_text(&encrypted_data))
59+
}
60+
61+
pub fn decode_hill(text: &str, key: Matrix2<i32>) -> Result<String, &'static str> {
62+
check_key_validity(&key)?;
63+
64+
let det = key[(0, 0)] * key[(1, 1)] - key[(0, 1)] * key[(1, 0)];
65+
let det_mod = det.rem_euclid(26);
66+
let det_inv = match mod_inv(det_mod, 26) {
67+
Some(x) => x,
68+
None => return Err("Error: key matrix is not invertible mod 26"),
69+
};
70+
71+
// compute the inverse using 2x2 formula
72+
let inv_key = Matrix2::new(
73+
(key[(1, 1)] * det_inv).rem_euclid(26),
74+
(-key[(0, 1)] * det_inv).rem_euclid(26),
75+
(-key[(1, 0)] * det_inv).rem_euclid(26),
76+
(key[(0, 0)] * det_inv).rem_euclid(26),
77+
);
78+
79+
let text_vector = text_to_numbers(text);
80+
let decrypted_data = process_2x2_chunks(text_vector, &inv_key);
81+
Ok(numbers_to_text(&decrypted_data))
82+
}
83+
84+
#[cfg(test)]
85+
mod tests {
86+
use super::*;
87+
88+
#[test]
89+
fn test_key_not_invertible_should_fail() {
90+
let key = Matrix2::new(2, 4, 1, 2);
91+
assert_eq!(
92+
encode_hill("test", key),
93+
Err("Error: key matrix is not invertible mod 26")
94+
);
95+
}
96+
97+
#[test]
98+
fn test_text_to_numbers_conversion() {
99+
assert_eq!(text_to_numbers("123 ABC xyz!?"), vec![0, 1, 2, 23, 24, 25]);
100+
}
101+
102+
#[test]
103+
fn test_numbers_to_text_conversion() {
104+
assert_eq!(numbers_to_text(&vec![0, 1, 2, 23, 24, 25]), "ABCXYZ");
105+
}
106+
107+
#[test]
108+
fn test_encoding_with_valid_key() {
109+
let key = Matrix2::new(3, 3, 2, 5);
110+
let result = encode_hill("HELP", key);
111+
assert!(result.is_ok());
112+
}
113+
114+
#[test]
115+
fn test_decoding_with_valid_key() {
116+
let key = Matrix2::new(3, 3, 2, 5);
117+
let encoded_text = encode_hill("HELP", key).unwrap();
118+
let decoded_text = decode_hill(&encoded_text, key).unwrap();
119+
assert_eq!(decoded_text, "HELP");
120+
}
121+
122+
#[test]
123+
fn test_encoding_with_padding() {
124+
let key = Matrix2::new(3, 3, 2, 5);
125+
let result = encode_hill("ABC", key);
126+
assert!(result.is_ok());
127+
}
128+
}

src/ciphers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ mod caesar;
77
mod chacha;
88
mod diffie_hellman;
99
mod hashing_traits;
10+
mod hill;
1011
mod kerninghan;
1112
mod morse_code;
1213
mod polybius;
@@ -30,6 +31,7 @@ pub use self::chacha::chacha20;
3031
pub use self::diffie_hellman::DiffieHellman;
3132
pub use self::hashing_traits::Hasher;
3233
pub use self::hashing_traits::HMAC;
34+
pub use self::hill::{decode_hill, encode_hill};
3335
pub use self::kerninghan::kerninghan;
3436
pub use self::morse_code::{decode, encode};
3537
pub use self::polybius::{decode_ascii, encode_ascii};

0 commit comments

Comments
 (0)