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
79fn 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
111fn 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
183fn 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
192fn 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 decoded_message.push_str(¤t_number);
230 }
231 } else {
232 decoded_message.push_str(¤t_number);
234 }
235 current_number.clear();
236 }
237 } else if character.is_digit(10) {
238 current_number.push(character);
239 } else {
240 decoded_message.push(character);
242 }
243 }
244
245 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(¤t_number);
253 }
254 } else {
255 decoded_message.push_str(¤t_number);
256 }
257 }
258
259 decoded_message
260}
261
262fn sha256hash(input: &str) -> String {
264 let mut hasher = Sha256::new();
265
266 hasher.update(input.as_bytes());
268
269 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
287pub 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 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
361fn 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