Skip to content

Commit 0573dbb

Browse files
feat: add Vernam cipher implementation (#984)
1 parent 35a5743 commit 0573dbb

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
* [Tea](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/tea.rs)
5555
* [Theoretical ROT13](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/theoretical_rot13.rs)
5656
* [Transposition](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/transposition.rs)
57+
* [Vernam](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/vernam.rs)
5758
* [Vigenere](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/vigenere.rs)
5859
* [XOR](https://github.com/TheAlgorithms/Rust/blob/master/src/ciphers/xor.rs)
5960
* Compression

src/ciphers/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ mod sha3;
1919
mod tea;
2020
mod theoretical_rot13;
2121
mod transposition;
22+
mod vernam;
2223
mod vigenere;
2324
mod xor;
2425

@@ -46,5 +47,6 @@ pub use self::sha3::{sha3_224, sha3_256, sha3_384, sha3_512};
4647
pub use self::tea::{tea_decrypt, tea_encrypt};
4748
pub use self::theoretical_rot13::theoretical_rot13;
4849
pub use self::transposition::transposition;
50+
pub use self::vernam::{vernam_decrypt, vernam_encrypt};
4951
pub use self::vigenere::vigenere;
5052
pub use self::xor::xor;

src/ciphers/vernam.rs

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
//! Vernam Cipher
2+
//!
3+
//! The Vernam cipher is a symmetric stream cipher where plaintext is combined
4+
//! with a random or pseudorandom stream of data (the key) of the same length.
5+
//! This implementation uses the alphabet A-Z with modular arithmetic.
6+
//!
7+
//! # Algorithm
8+
//!
9+
//! For encryption: C = (P + K) mod 26
10+
//! For decryption: P = (C - K) mod 26
11+
//!
12+
//! Where P is plaintext, K is key, and C is ciphertext (all converted to 0-25 range)
13+
14+
/// Encrypts a plaintext string using the Vernam cipher.
15+
///
16+
/// The function converts all input to uppercase and works only with letters A-Z.
17+
/// The key is repeated cyclically if it's shorter than the plaintext.
18+
///
19+
/// # Arguments
20+
///
21+
/// * `plaintext` - The text to encrypt (will be converted to uppercase)
22+
/// * `key` - The encryption key (will be converted to uppercase, must not be empty)
23+
///
24+
/// # Returns
25+
///
26+
/// The encrypted ciphertext as an uppercase string
27+
///
28+
/// # Panics
29+
///
30+
/// Panics if the key is empty
31+
///
32+
/// # Example
33+
///
34+
/// ```
35+
/// use the_algorithms_rust::ciphers::vernam_encrypt;
36+
///
37+
/// let ciphertext = vernam_encrypt("HELLO", "KEY");
38+
/// assert_eq!(ciphertext, "RIJVS");
39+
/// ```
40+
pub fn vernam_encrypt(plaintext: &str, key: &str) -> String {
41+
assert!(!key.is_empty(), "Key cannot be empty");
42+
43+
let plaintext = plaintext.to_uppercase();
44+
let key = key.to_uppercase();
45+
46+
let plaintext_bytes: Vec<u8> = plaintext
47+
.chars()
48+
.filter(|c| c.is_ascii_alphabetic())
49+
.map(|c| (c as u8) - b'A')
50+
.collect();
51+
52+
let key_bytes: Vec<u8> = key
53+
.chars()
54+
.filter(|c| c.is_ascii_alphabetic())
55+
.map(|c| (c as u8) - b'A')
56+
.collect();
57+
58+
assert!(
59+
!key_bytes.is_empty(),
60+
"Key must contain at least one letter"
61+
);
62+
63+
plaintext_bytes
64+
.iter()
65+
.enumerate()
66+
.map(|(i, &p)| {
67+
let k = key_bytes[i % key_bytes.len()];
68+
let encrypted = (p + k) % 26;
69+
(encrypted + b'A') as char
70+
})
71+
.collect()
72+
}
73+
74+
/// Decrypts a ciphertext string using the Vernam cipher.
75+
///
76+
/// The function converts all input to uppercase and works only with letters A-Z.
77+
/// The key is repeated cyclically if it's shorter than the ciphertext.
78+
///
79+
/// # Arguments
80+
///
81+
/// * `ciphertext` - The text to decrypt (will be converted to uppercase)
82+
/// * `key` - The decryption key (will be converted to uppercase, must not be empty)
83+
///
84+
/// # Returns
85+
///
86+
/// The decrypted plaintext as an uppercase string
87+
///
88+
/// # Panics
89+
///
90+
/// Panics if the key is empty
91+
///
92+
/// # Example
93+
///
94+
/// ```
95+
/// use the_algorithms_rust::ciphers::vernam_decrypt;
96+
///
97+
/// let plaintext = vernam_decrypt("RIJVS", "KEY");
98+
/// assert_eq!(plaintext, "HELLO");
99+
/// ```
100+
pub fn vernam_decrypt(ciphertext: &str, key: &str) -> String {
101+
assert!(!key.is_empty(), "Key cannot be empty");
102+
103+
let ciphertext = ciphertext.to_uppercase();
104+
let key = key.to_uppercase();
105+
106+
let ciphertext_bytes: Vec<u8> = ciphertext
107+
.chars()
108+
.filter(|c| c.is_ascii_alphabetic())
109+
.map(|c| (c as u8) - b'A')
110+
.collect();
111+
112+
let key_bytes: Vec<u8> = key
113+
.chars()
114+
.filter(|c| c.is_ascii_alphabetic())
115+
.map(|c| (c as u8) - b'A')
116+
.collect();
117+
118+
assert!(
119+
!key_bytes.is_empty(),
120+
"Key must contain at least one letter"
121+
);
122+
123+
ciphertext_bytes
124+
.iter()
125+
.enumerate()
126+
.map(|(i, &c)| {
127+
let k = key_bytes[i % key_bytes.len()];
128+
// Add 26 before modulo to handle negative numbers properly
129+
let decrypted = (c + 26 - k) % 26;
130+
(decrypted + b'A') as char
131+
})
132+
.collect()
133+
}
134+
135+
#[cfg(test)]
136+
mod tests {
137+
use super::*;
138+
139+
#[test]
140+
fn test_encrypt_basic() {
141+
assert_eq!(vernam_encrypt("HELLO", "KEY"), "RIJVS");
142+
}
143+
144+
#[test]
145+
fn test_decrypt_basic() {
146+
assert_eq!(vernam_decrypt("RIJVS", "KEY"), "HELLO");
147+
}
148+
149+
#[test]
150+
fn test_encrypt_decrypt_roundtrip() {
151+
let plaintext = "HELLO";
152+
let key = "KEY";
153+
let encrypted = vernam_encrypt(plaintext, key);
154+
let decrypted = vernam_decrypt(&encrypted, key);
155+
assert_eq!(decrypted, plaintext);
156+
}
157+
158+
#[test]
159+
fn test_encrypt_decrypt_long_text() {
160+
let plaintext = "THEQUICKBROWNFOXJUMPSOVERTHELAZYDOG";
161+
let key = "SECRET";
162+
let encrypted = vernam_encrypt(plaintext, key);
163+
let decrypted = vernam_decrypt(&encrypted, key);
164+
assert_eq!(decrypted, plaintext);
165+
}
166+
167+
#[test]
168+
fn test_lowercase_input() {
169+
// Should convert to uppercase
170+
assert_eq!(vernam_encrypt("hello", "key"), "RIJVS");
171+
assert_eq!(vernam_decrypt("rijvs", "key"), "HELLO");
172+
}
173+
174+
#[test]
175+
fn test_mixed_case_input() {
176+
assert_eq!(vernam_encrypt("HeLLo", "KeY"), "RIJVS");
177+
assert_eq!(vernam_decrypt("RiJvS", "kEy"), "HELLO");
178+
}
179+
180+
#[test]
181+
fn test_single_character() {
182+
assert_eq!(vernam_encrypt("A", "B"), "B");
183+
assert_eq!(vernam_decrypt("B", "B"), "A");
184+
}
185+
186+
#[test]
187+
fn test_key_wrapping() {
188+
// Key shorter than plaintext, should wrap around
189+
let encrypted = vernam_encrypt("AAAA", "BC");
190+
assert_eq!(encrypted, "BCBC");
191+
let decrypted = vernam_decrypt(&encrypted, "BC");
192+
assert_eq!(decrypted, "AAAA");
193+
}
194+
195+
#[test]
196+
fn test_alphabet_boundary() {
197+
// Test wrapping at alphabet boundaries
198+
assert_eq!(vernam_encrypt("Z", "B"), "A"); // 25 + 1 = 26 -> 0
199+
assert_eq!(vernam_decrypt("A", "B"), "Z"); // 0 - 1 = -1 -> 25
200+
}
201+
202+
#[test]
203+
fn test_same_key_as_plaintext() {
204+
let text = "HELLO";
205+
let encrypted = vernam_encrypt(text, text);
206+
assert_eq!(encrypted, "OIWWC");
207+
}
208+
209+
#[test]
210+
fn test_with_spaces_and_numbers() {
211+
// Non-alphabetic characters should be filtered out
212+
let encrypted = vernam_encrypt("HELLO 123 WORLD", "KEY");
213+
let expected = vernam_encrypt("HELLOWORLD", "KEY");
214+
assert_eq!(encrypted, expected);
215+
}
216+
217+
#[test]
218+
#[should_panic(expected = "Key cannot be empty")]
219+
fn test_empty_key_encrypt() {
220+
vernam_encrypt("HELLO", "");
221+
}
222+
223+
#[test]
224+
#[should_panic(expected = "Key cannot be empty")]
225+
fn test_empty_key_decrypt() {
226+
vernam_decrypt("HELLO", "");
227+
}
228+
229+
#[test]
230+
#[should_panic(expected = "Key must contain at least one letter")]
231+
fn test_key_with_only_numbers() {
232+
vernam_encrypt("HELLO", "12345");
233+
}
234+
235+
#[test]
236+
fn test_empty_plaintext() {
237+
assert_eq!(vernam_encrypt("", "KEY"), "");
238+
}
239+
240+
#[test]
241+
fn test_plaintext_with_only_numbers() {
242+
assert_eq!(vernam_encrypt("12345", "KEY"), "");
243+
}
244+
}

0 commit comments

Comments
 (0)