kryptor/
lib.rs

1use clap::{App, Arg};
2use std::error::Error;
3use std::fs::File;
4use std::io::{self, BufRead, BufReader};
5use base32::{Alphabet, encode, decode};
6use sha2::{Digest, Sha256, Sha512};
7
8type MyResult<T> = Result<T, Box<dyn Error>>;
9
10#[derive(Debug)]
11pub struct Config {
12    files: Vec<String>,
13    encode_type: Option<String>,
14    decode_type: Option<String>,
15    hash: Option<String>,
16    hexdump: Option<bool>
17}
18
19pub fn get_args() -> MyResult<Config> {
20    let matches = App::new("Kryptor")
21        .version("0.1.3")
22        .about("A simple command-line cryptography tool")
23        .arg(
24            Arg::with_name("files")
25                .value_name("FILE")
26                .help("Input file(s) or leave blank to recieve input from stdin")
27                .multiple(true)
28                .default_value("-"),
29        )
30        .arg(
31            Arg::with_name("decode")
32                .short("d")
33                .long("decode")
34                .takes_value(true)
35                .possible_values(&["base32","base64","hex","ROT13","A1Z26"])
36                .conflicts_with("encode")
37                .help("decode text to the format specified"),
38        )
39        .arg(
40            Arg::with_name("encode")
41                .short("e")
42                .long("encode")
43                .takes_value(true)
44                .possible_values(&["base32","base64","hex","ROT13","A1Z26"])
45                .conflicts_with("decode")
46                .help("encode to the format specified"),
47        )
48        .arg(
49            Arg::with_name("hash")
50                .short("s")
51                .long("hash")
52                .takes_value(true)
53                .possible_values(&["SHA256","SHA512"])
54                .help("hash the strings in the file")
55        )
56        .arg(
57            Arg::with_name("hexdump")
58                .short("p")
59                .long("hexdump")
60                .takes_value(false)
61                .help("hexdump the file specified")
62        )
63        .get_matches();
64    
65    let files: Vec<String> = matches
66        .values_of("files")
67        .unwrap_or_default()
68        .map(String::from)
69        .collect();
70    
71    let encode_type = matches.value_of("encode").map(String::from);
72    let decode_type = matches.value_of("decode").map(String::from);
73    let hash = matches.value_of("hash").map(String::from);
74    let hexdump = if matches.is_present("hexdump") { Some(true) } else {None};
75
76    Ok(Config { files, encode_type, decode_type, hash, hexdump })
77} 
78
79// decode-encodings--------------------------------------------
80fn base64_encode(input: &str) -> String {
81    base64::encode(input)
82}
83
84fn base64_decode(input: &str) -> String {
85    match base64::decode(input) {
86        Ok(decoded) => String::from_utf8_lossy(&decoded).to_string(),
87        Err(_) => {
88            eprintln!("Error decoding base64.");
89            input.to_string()
90        }
91    }
92}
93
94fn base32_encode(input: &str) -> String {
95    let encoded = encode(Alphabet::RFC4648 { padding: false }, input.as_bytes());
96    encoded
97}
98
99fn base32_decode(input: &str) -> Result<String, String> {
100    match decode(Alphabet::RFC4648 { padding: false }, input) {
101        Some(decoded_data) => {
102            let decoded_string = String::from_utf8_lossy(&decoded_data).to_string();
103            Ok(decoded_string)
104        }
105        None => {
106            Err("Error decoding Base32".to_string())
107        }
108    }
109}
110
111// for handling base32_decode()
112fn decode_base32_handler(input: &str) -> String {
113    match base32_decode(input) {
114        Ok(decoded_string) => decoded_string,
115        Err(error) => {
116            eprintln!("Error: {}", error); 
117            String::from("Decoding error") 
118        }
119    }
120}
121
122fn to_hex(input: &str) -> String {
123    let mut hex_string = String::new();
124
125    for byte in input.bytes() {
126        hex_string.push_str(&format!("{:02X}", byte));
127    }
128
129    hex_string
130}
131
132fn from_hex(hex_input: &str) -> Result<String, &'static str> {
133    if hex_input.len() % 2 != 0 {
134        return Err("Invalid hex string length");
135    }
136
137    let mut decoded_string = String::new();
138    let mut byte_str = String::new();
139
140    for (i, hex_char) in hex_input.chars().enumerate() {
141        byte_str.push(hex_char);
142
143        if i % 2 == 1 {
144            if let Ok(byte) = u8::from_str_radix(&byte_str, 16) {
145                decoded_string.push(byte as char);
146            } else {
147                return Err("Invalid hex character");
148            }
149
150            byte_str.clear();
151        }
152    }
153
154    Ok(decoded_string)
155}
156
157fn hexdump(input: &str) -> String {
158    let mut hexdump_string = String::new();
159
160    for (i, byte) in input.bytes().enumerate() {
161        if i > 0 && i % 16 == 0 {
162            hexdump_string.push('\n');
163        } else if i > 0 {
164            hexdump_string.push(' ');
165        }
166
167        hexdump_string.push_str(&format!("{:02X}", byte));
168    }
169
170    hexdump_string.push('\n');
171
172    for byte in input.bytes() {
173        if byte >= 32 && byte < 127 {
174            hexdump_string.push(byte as char);
175        } else {
176            hexdump_string.push('.');
177        }
178    }
179
180    hexdump_string
181}
182
183// ciphering-------------------------------------------------
184fn rot13_char(c: char) -> char {
185    match c {
186        'a'..='z' => ((((c as u8 - b'a') + 13) % 26) + b'a') as char,
187        'A'..='Z' => ((((c as u8 - b'A') + 13) % 26) + b'A') as char,
188        _ => c,
189    }
190}
191
192// for handling rot13_char()
193fn to_rot13(input: &str) -> String {
194    format!("{}",input.chars().map(rot13_char).collect::<String>())
195}
196
197fn a1z26_encode(input: &str) -> String {
198    let mut encoded_message = String::new();
199
200    for character in input.chars() {
201        if character.is_alphabetic() {
202            let char_upper = character.to_ascii_uppercase();
203            let char_code = (char_upper as u8 - b'A' + 1).to_string();
204            encoded_message.push_str(&char_code);
205            encoded_message.push('-');
206        } else {
207            encoded_message.push(character);
208        }
209    }
210    if encoded_message.ends_with('-') {
211        encoded_message.pop();
212    }
213    encoded_message
214}
215
216fn a1z26_decode(input: &str) -> String {
217    let mut decoded_message = String::new();
218    let mut current_number = String::new();
219
220    for character in input.chars() {
221        if character == '-' {
222            if !current_number.is_empty() {
223                if let Ok(number) = current_number.parse::<usize>() {
224                    if number >= 1 && number <= 26 {
225                        let decoded_char = (b'A' + (number - 1) as u8) as char;
226                        decoded_message.push(decoded_char);
227                    } else {
228                        // Invalid code
229                        decoded_message.push_str(&current_number);
230                    }
231                } else {
232                    // Failed to parse as a number
233                    decoded_message.push_str(&current_number);
234                }
235                current_number.clear();
236            }
237        } else if character.is_digit(10) {
238            current_number.push(character);
239        } else {
240            // If the character is not a digit or hyphen, add it
241            decoded_message.push(character);
242        }
243    }
244
245    // Handle the last number
246    if !current_number.is_empty() {
247        if let Ok(number) = current_number.parse::<usize>() {
248            if number >= 1 && number <= 26 {
249                let decoded_char = (b'A' + (number - 1) as u8) as char;
250                decoded_message.push(decoded_char);
251            } else {
252                decoded_message.push_str(&current_number);
253            }
254        } else {
255            decoded_message.push_str(&current_number);
256        }
257    }
258
259    decoded_message
260}
261
262// hashing-----------------------------------------------------
263fn sha256hash(input: &str) -> String {
264    let mut hasher = Sha256::new();
265
266    // Update the hasher with the input bytes
267    hasher.update(input.as_bytes());
268
269    // Calculate the hash and convert it to a hex string
270    let result = hasher.finalize();
271    let hash_hex = result.iter().map(|byte| format!("{:02x}", byte)).collect::<String>();
272
273    hash_hex
274}
275
276fn sha512hash(input: &str) -> String {
277    let mut hasher = Sha512::new();
278
279    hasher.update(input.as_bytes());
280
281    let result = hasher.finalize();
282    let hash_hex = result.iter().map(|byte| format!("{:02x}", byte)).collect::<String>();
283
284    hash_hex
285}
286
287// run function-------------------------------------------------
288pub fn run(config: Config) -> MyResult<()> {
289    for filename in &config.files {
290        match open(&filename) {
291            Err(err) => eprintln!("{}: {}", filename, err),
292            Ok(file) => {
293                let is_hexdump = config.hexdump.is_some();
294                    
295                    // Iterate through the lines in the file(s)
296                    for line_result in file.lines() {
297                        let line = line_result?;
298                        let result = if is_hexdump {
299                            hexdump(&line)
300                        } else if config.encode_type.is_some() {
301                                if let Some(encoding) = &config.encode_type {
302                                    match encoding.as_str() {
303                                        "base64" => base64_encode(&line),
304                                        "ROT13" => to_rot13(&line),
305                                        "base32" => base32_encode(&line),
306                                        "A1Z26" => a1z26_encode(&line),
307                                        "hex" => to_hex(&line),
308                                        _ => line.to_string(),
309                                    }
310                                } else {
311                                    eprintln!("Encoding type not specified.");
312                                    return Ok(());
313                                }
314                            } else if config.decode_type.is_some() {
315                                if let Some(decoding) = &config.decode_type {
316                                    match decoding.as_str() {
317                                        "base64" => base64_decode(&line),
318                                        "ROT13" => to_rot13(&line),
319                                        "base32" => decode_base32_handler(&line),
320                                        "A1Z26" => a1z26_decode(&line),
321                                        "hex" => {
322                                            match from_hex(&line) {
323                                                Ok(decoded) => decoded,
324                                                Err(err) => {
325                                                    eprintln!("Error decoding hex: {}", err);
326                                                    line.to_string()
327                                                }
328                                            }
329                                        }
330                                        _ => line.to_string(),
331                                    }
332                                } else {
333                                    eprintln!("Decoding type not specified.");
334                                    return Ok(());
335                                }
336                            } else if config.hash.is_some() {
337                                if let Some(hashing) = &config.hash {
338                                    match hashing.as_str() {
339                                        "SHA256" => sha256hash(&line),
340                                        "SHA512" => sha512hash(&line),
341                                        _ => line.to_string(),
342                                    }
343                                } else {
344                                    eprintln!("Hashing format not specified.");
345                                    return Ok(());
346                                }
347                            } else {
348                                eprintln!("flags must be specified.");
349                                return Ok(());
350                            };
351
352                        println!("{}", result);
353                    }
354                
355            }
356        }
357    }
358    Ok(())
359}
360
361// open function-----------------------------------------------------
362fn open(filename: &str) -> MyResult<Box<dyn BufRead>> {
363    match filename {
364        "-" => Ok(Box::new( BufReader::new(io::stdin()))),
365        _ => Ok(Box::new( BufReader::new(File::open(filename)?))),
366    }
367}
368