Skip to main content

base64_cipher/
lib.rs

1use itertools::Itertools as _;
2
3pub struct Base64;
4
5const CHARACTERS: &[u8] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".as_bytes();
6
7impl Base64 {
8    pub fn encrypt(plaintext: &str) -> String {
9        plaintext
10            .replace("\r", "")
11            .chars()
12            .chunks(3)
13            .into_iter()
14            .map(|triplet| {
15                let mut quadruplet = triplet
16                    .map(|character| format!("{:08b}", character as u8))
17                    .join("")
18                    .chars()
19                    .chunks(6)
20                    .into_iter()
21                    .map(|chunk| {
22                        let mut string = chunk.collect::<String>();
23                        while string.len() < 6 {
24                            string = string + "0";
25                        }
26                        (*CHARACTERS.get(usize::from_str_radix(&string, 2).unwrap()).unwrap() as char).to_string()
27                    })
28                    .collect::<String>();
29                while quadruplet.len() % 4 != 0 {
30                    quadruplet += "=";
31                }
32                quadruplet
33            })
34            .collect()
35    }
36
37    pub fn decrypt(ciphertext: &str) -> String {
38        ciphertext
39            .chars()
40            .filter(|character| !character.is_whitespace())
41            .chunks(4)
42            .into_iter()
43            .map(|quadruplet| {
44                quadruplet
45                    .map(|character| {
46                        if character == '=' {
47                            "2".to_owned()
48                        } else {
49                            format!("{:06b}", CHARACTERS.iter().position(|other| *other as char == character).unwrap())
50                        }
51                    })
52                    .join("")
53                    .chars()
54                    .chunks(8)
55                    .into_iter()
56                    .filter_map(|chunk| {
57                        let string = chunk.collect::<String>().trim_end_matches("2").to_owned();
58                        (string.len() == 8).then_some(u8::from_str_radix(&string, 2).unwrap() as char)
59                    })
60                    .collect::<String>()
61            })
62            .collect()
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use crate::Base64;
69
70    #[test]
71    fn encrypt_decrypt() {
72        let letter = include_str!("../tests/letter.txt").trim().replace("\r", "");
73        let encrypted_letter = include_str!("../tests/encrypted_letter.txt").trim().replace("\r", "");
74
75        let ciphertext = Base64::encrypt(&letter);
76        let plaintext = Base64::decrypt(&encrypted_letter);
77
78        assert_eq!(letter, plaintext);
79        assert_eq!(encrypted_letter, ciphertext);
80    }
81}