Skip to main content

happy_cracking/crypto/
columnar.rs

1use anyhow::Result;
2use clap::Subcommand;
3
4use super::shared::column_order;
5
6#[derive(Subcommand)]
7pub enum ColumnarAction {
8    #[command(about = "Encrypt with Columnar Transposition cipher")]
9    Encrypt {
10        #[arg(help = "Input text")]
11        input: String,
12        #[arg(short, long, help = "Keyword for column ordering")]
13        key: String,
14    },
15    #[command(about = "Decrypt Columnar Transposition cipher")]
16    Decrypt {
17        #[arg(help = "Encrypted text")]
18        input: String,
19        #[arg(short, long, help = "Keyword for column ordering")]
20        key: String,
21    },
22}
23
24pub fn run(action: ColumnarAction) -> Result<()> {
25    match action {
26        ColumnarAction::Encrypt { input, key } => {
27            println!("{}", encrypt(&input, &key)?);
28        }
29        ColumnarAction::Decrypt { input, key } => {
30            println!("{}", decrypt(&input, &key)?);
31        }
32    }
33    Ok(())
34}
35
36// Safety limit to prevent massive memory allocation (DoS).
37// If a user provides a tiny input but a massive key (e.g. 100 million chars),
38// the padding logic would allocate 100 million 'X's.
39const MAX_KEY_LEN: usize = 1_000_000;
40
41pub fn encrypt(input: &str, key: &str) -> Result<String> {
42    if key.len() > MAX_KEY_LEN {
43        anyhow::bail!(
44            "Key exceeds maximum length of {} to prevent Denial of Service",
45            MAX_KEY_LEN
46        );
47    }
48
49    if key.is_empty() || !key.chars().all(|c| c.is_ascii_alphabetic()) {
50        anyhow::bail!("Key must be non-empty and contain only alphabetic characters");
51    }
52
53    if input.is_empty() {
54        return Ok(String::new());
55    }
56
57    let key_len = key.len();
58    let order = column_order(key);
59
60    // Pad input with 'X' to fill the grid
61    let mut padded: Vec<char> = input.chars().collect();
62    while !padded.len().is_multiple_of(key_len) {
63        padded.push('X');
64    }
65
66    // Build grid row by row
67    let grid: Vec<Vec<char>> = padded.chunks(key_len).map(|row| row.to_vec()).collect();
68
69    // Read columns in key order
70    let mut sorted_cols: Vec<usize> = (0..key_len).collect();
71    sorted_cols.sort_by_key(|&col| order[col]);
72
73    let mut result = String::new();
74    for &col in &sorted_cols {
75        for row in &grid {
76            result.push(row[col]);
77        }
78    }
79
80    Ok(result)
81}
82
83pub fn decrypt(input: &str, key: &str) -> Result<String> {
84    if key.len() > MAX_KEY_LEN {
85        anyhow::bail!(
86            "Key exceeds maximum length of {} to prevent Denial of Service",
87            MAX_KEY_LEN
88        );
89    }
90
91    if key.is_empty() || !key.chars().all(|c| c.is_ascii_alphabetic()) {
92        anyhow::bail!("Key must be non-empty and contain only alphabetic characters");
93    }
94
95    if input.is_empty() {
96        return Ok(String::new());
97    }
98
99    let key_len = key.len();
100    let chars: Vec<char> = input.chars().collect();
101    let total_len = chars.len();
102
103    if !total_len.is_multiple_of(key_len) {
104        anyhow::bail!("Ciphertext length must be a multiple of key length");
105    }
106
107    let num_rows = total_len / key_len;
108    let order = column_order(key);
109
110    // Determine the order in which columns appear in ciphertext
111    let mut sorted_cols: Vec<usize> = (0..key_len).collect();
112    sorted_cols.sort_by_key(|&col| order[col]);
113
114    // Fill columns in key order
115    let mut columns: Vec<Vec<char>> = vec![Vec::new(); key_len];
116    let mut pos = 0;
117    for &col in &sorted_cols {
118        columns[col] = chars[pos..pos + num_rows].to_vec();
119        pos += num_rows;
120    }
121
122    // Read row by row
123    let mut result = String::new();
124    for row in 0..num_rows {
125        for column in &columns {
126            result.push(column[row]);
127        }
128    }
129
130    Ok(result)
131}