diff --git a/src/base64bytes.rs b/src/base64bytes.rs deleted file mode 100644 index f7b096f..0000000 --- a/src/base64bytes.rs +++ /dev/null @@ -1,37 +0,0 @@ -pub struct Base64Bytes(pub Vec); -use std::str; - -impl std::fmt::Display for Base64Bytes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Base64({})", self.to_base64_string()) - } -} - -impl Base64Bytes { - pub fn to_base64_string(&self) -> String { - let Base64Bytes(digits) = self; - let mut r: Vec = digits - .iter() - .map(|d| match d { - 0..=25 => *d + ('A' as u8), - 26..=51 => *d - 26 + ('a' as u8), - 52..=61 => *d - 52 + ('0' as u8), - 62 => '+' as u8, - 63 => '/' as u8, - _ => panic!("Unexpected base64 digit '{}'", d), - }) - .collect(); - // Handle padding - let pad = '=' as u8; - match r.len() % 4 { - 0 => (), - 2 => { - r.push(pad); - r.push(pad); - } - 3 => r.push(pad), - _ => (), - } - str::from_utf8(r.as_slice()).unwrap().to_string() - } -} diff --git a/src/bytes.rs b/src/bytes.rs new file mode 100644 index 0000000..591dd4f --- /dev/null +++ b/src/bytes.rs @@ -0,0 +1,96 @@ +use std::fmt::Write; // need to import this trait + +#[derive(PartialEq, PartialOrd, Debug)] +pub struct Bytes(pub Vec); + +impl Bytes { + pub fn from_utf8(s: &str) -> Bytes { + Bytes(s.as_bytes().iter().map(|c| c.clone()).collect()) + } + + #[allow(dead_code)] + pub fn to_utf8(&self) -> String { + let Bytes(v) = self; + String::from(std::str::from_utf8(&v).unwrap()) + } + + pub fn from_hex(s: &str) -> Bytes { + if s.len() % 2 != 0 { + panic!("Input string has uneven number of characters"); + } + + let bytes_result: Result, std::num::ParseIntError> = (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) + .collect(); + + match bytes_result { + Ok(b) => Bytes(b), + Err(_) => panic!("Could not convert all digit pairs to hex."), + } + } + + pub fn to_hex(&self) -> String { + let Bytes(v) = self; + let mut r = String::new(); + for e in v.iter() { + write!(r, "{:02x}", e); + } + r + } + + pub fn is_ascii(&self) -> bool { + let Bytes(v) = self; + for &c in v.iter() { + if c < 32 || c > 127 { + return false; + } + } + true + } + + pub fn ascii_score(&self) -> u32 { + let Bytes(v) = self; + let mut r = 0; + for &c in v.iter() { + match c { + 32 => r += 2, + 33..=64 => r += 1, + 65..=90 => r += 3, + 91..=96 => r += 1, + 97..=122 => r += 3, + 123..=127 => r += 1, + _ => (), + } + } + r + } + + pub fn xor(Bytes(a): &Bytes, Bytes(b): &Bytes) -> Bytes { + Bytes( + Iterator::zip(a.iter(), b.iter()) + .map(|z| *(z.0) ^ *(z.1)) + .collect(), + ) + } + + pub fn xor_byte(Bytes(a): &Bytes, byte: u8) -> Bytes { + Bytes(a.iter().map(|e| e ^ byte).collect()) + } + + pub fn xor_cycle(Bytes(msg): &Bytes, Bytes(key): &Bytes) -> Bytes { + Bytes( + Iterator::zip(msg.iter(), 0..msg.len()) + .map(|z| *(z.0) ^ key[z.1 % key.len()]) + .collect(), + ) + } + + #[allow(dead_code)] + pub fn hemming(Bytes(a): &Bytes, Bytes(b): &Bytes) -> u32 { + let v: Vec = Iterator::zip(a.iter(), b.iter()) + .map(|z| (*(z.0) ^ *(z.1)).count_ones()) + .collect(); + v.iter().sum() + } +} diff --git a/src/bytes_base64.rs b/src/bytes_base64.rs new file mode 100644 index 0000000..aab2173 --- /dev/null +++ b/src/bytes_base64.rs @@ -0,0 +1,62 @@ +use crate::bytes::Bytes; +use std::str; + +pub struct BytesBase64(pub Vec); + +impl std::fmt::Display for BytesBase64 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Base64({})", self.to_string()) + } +} + +impl BytesBase64 { + pub fn from_bytes(Bytes(bytes): Bytes) -> BytesBase64 { + fn chunk_to_base64(c: &[u8]) -> Vec { + let (value, iterations) = match c.len() { + 0 => return vec![], + 1 => ((c[0] as u32) << 16, 2), + 2 => ((c[0] as u32) << 16 | (c[1] as u32) << 8, 3), + 3 => ((c[0] as u32) << 16 | (c[1] as u32) << 8 | (c[2] as u32), 4), + _ => panic!("Unexpected number of chunks {}.", c.len()), + }; + + (0..iterations) + .map(|i| (value.rotate_right((3 - i) * 6) & 0b111111) as u8) + .collect() + } + BytesBase64( + bytes + .chunks(3) + .map(|c| chunk_to_base64(c)) + .flatten() + .collect(), + ) + } + + pub fn to_string(&self) -> String { + let BytesBase64(digits) = self; + let mut r: Vec = digits + .iter() + .map(|d| match d { + 0..=25 => *d + ('A' as u8), + 26..=51 => *d - 26 + ('a' as u8), + 52..=61 => *d - 52 + ('0' as u8), + 62 => '+' as u8, + 63 => '/' as u8, + _ => panic!("Unexpected base64 digit '{}'", d), + }) + .collect(); + // Handle padding + let pad = '=' as u8; + match r.len() % 4 { + 0 => (), + 2 => { + r.push(pad); + r.push(pad); + } + 3 => r.push(pad), + _ => (), + } + str::from_utf8(r.as_slice()).unwrap().to_string() + } +} diff --git a/src/hexbytes.rs b/src/hexbytes.rs deleted file mode 100644 index 3d79487..0000000 --- a/src/hexbytes.rs +++ /dev/null @@ -1,115 +0,0 @@ -use crate::base64bytes::Base64Bytes; -use std::fmt::Write; -use std::num::ParseIntError; -use std::str; - -#[derive(PartialEq, PartialOrd, Debug)] -pub struct HexBytes(Vec); - -impl HexBytes { - pub fn to_base64(&self) -> Base64Bytes { - fn chunk_to_base64(c: &[u8]) -> Vec { - let (value, iterations) = match c.len() { - 0 => return vec![], - 1 => ((c[0] as u32) << 16, 2), - 2 => ((c[0] as u32) << 16 | (c[1] as u32) << 8, 3), - 3 => ((c[0] as u32) << 16 | (c[1] as u32) << 8 | (c[2] as u32), 4), - _ => panic!("Unexpected number of chunks {}.", c.len()), - }; - - (0..iterations) - .map(|i| (value.rotate_right((3 - i) * 6) & 0b111111) as u8) - .collect() - } - let HexBytes(bytes) = self; - Base64Bytes( - bytes - .chunks(3) - .map(|c| chunk_to_base64(c)) - .flatten() - .collect(), - ) - } - - pub fn from_str(s: &str) -> HexBytes { - HexBytes(s.as_bytes().iter().map(|c| c.clone()).collect()) - } - - pub fn from_hex_str(s: &str) -> HexBytes { - if s.len() % 2 != 0 { - panic!("Input string has uneven number of characters"); - } - - let bytes_result: Result, ParseIntError> = (0..s.len()) - .step_by(2) - .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) - .collect(); - - match bytes_result { - Ok(b) => HexBytes(b), - Err(_) => panic!("Could not convert all digit pairs to hex."), - } - } - - #[allow(dead_code)] - pub fn to_utf8_string(&self) -> String { - let HexBytes(v) = self; - String::from(str::from_utf8(&v).unwrap()) - } - - pub fn to_hex_string(&self) -> String { - let HexBytes(v) = self; - let mut r = String::new(); - for e in v.iter() { - write!(r, "{:02x}", e); - } - r - } - - pub fn is_ascii(&self) -> bool { - let HexBytes(v) = self; - for &c in v.iter() { - if c < 32 || c > 127 { - return false; - } - } - true - } - - pub fn ascii_score(&self) -> u32 { - let HexBytes(v) = self; - let mut r = 0; - for &c in v.iter() { - match c { - 32 => r += 2, - 33..=64 => r += 1, - 65..=90 => r += 3, - 91..=96 => r += 1, - 97..=122 => r += 3, - 123..=127 => r += 1, - _ => (), - } - } - r - } - - pub fn xor(HexBytes(a): &HexBytes, HexBytes(b): &HexBytes) -> HexBytes { - HexBytes( - Iterator::zip(a.iter(), b.iter()) - .map(|z| *(z.0) ^ *(z.1)) - .collect(), - ) - } - - pub fn xor_byte(HexBytes(a): &HexBytes, byte: u8) -> HexBytes { - HexBytes(a.iter().map(|e| e ^ byte).collect()) - } - - pub fn xor_cycle(HexBytes(msg): &HexBytes, HexBytes(key): &HexBytes) -> HexBytes { - HexBytes( - Iterator::zip(msg.iter(), 0..msg.len()) - .map(|z| *(z.0) ^ key[z.1 % key.len()]) - .collect(), - ) - } -} diff --git a/src/main.rs b/src/main.rs index ece9baa..1539d9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -mod base64bytes; -mod hexbytes; +mod bytes; +mod bytes_base64; mod set1; fn main() { diff --git a/src/set1.rs b/src/set1.rs index e124560..b895225 100644 --- a/src/set1.rs +++ b/src/set1.rs @@ -1,78 +1,77 @@ -use crate::hexbytes::HexBytes; +use crate::bytes::Bytes; +use crate::bytes_base64::BytesBase64; use std::fs::File; use std::io::{BufRead, BufReader}; use std::str; pub fn challenge1() { - let a = HexBytes::from_hex_str("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"); - let e = String::from("SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t"); - let r = a.to_base64(); - if r.to_base64_string() == e { - println!("[okay] Challenge 1: {}", r); + let input = Bytes::from_hex("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d"); + let expected = String::from("SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t"); + let result = BytesBase64::from_bytes(input); + if result.to_string() == expected { + println!("[okay] Challenge 1: {}", result); } else { println!("[fail] Challenge 1") } } pub fn challenge2() { - let a = HexBytes::from_hex_str("1c0111001f010100061a024b53535009181c"); - let b = HexBytes::from_hex_str("686974207468652062756c6c277320657965"); - let r = HexBytes::xor(&a, &b); - let e = HexBytes::from_hex_str("746865206b696420646f6e277420706c6179"); - if r == e { - println!("[okay] Challenge 2: {}", r.to_hex_string()); + let input_1 = Bytes::from_hex("1c0111001f010100061a024b53535009181c"); + let input_2 = Bytes::from_hex("686974207468652062756c6c277320657965"); + let result = Bytes::xor(&input_1, &input_2); + let expected = Bytes::from_hex("746865206b696420646f6e277420706c6179"); + if result == expected { + println!("[okay] Challenge 2: {}", result.to_hex()); } else { println!("[fail] Challenge 2") } } pub fn challenge3() { - let a = HexBytes::from_hex_str( - "1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736", - ); - - let mut h: Vec = (0..=255).map(|i| HexBytes::xor_byte(&a, i)).collect(); + let a = Bytes::from_hex("1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736"); + let mut h: Vec = (0..=255).map(|i| Bytes::xor_byte(&a, i)).collect(); h.sort_by(|a, b| a.ascii_score().partial_cmp(&b.ascii_score()).unwrap()); - let h: Vec = h.into_iter().filter(|b| b.is_ascii()).collect(); - let r = h[h.len() - 1].to_utf8_string(); + let h: Vec = h.into_iter().filter(|b| b.is_ascii()).collect(); + let r = h[h.len() - 1].to_utf8(); println!("[okay] Challenge 3: {}", r); } pub fn challenge4() { - pub fn read_to_vector(path: &str) -> Vec { + pub fn read_to_vector(path: &str) -> Vec { let file = File::open(path).unwrap(); let br = BufReader::new(file); br.lines() - .map(|line| HexBytes::from_hex_str(&line.unwrap())) + .map(|line| Bytes::from_hex(&line.unwrap())) .collect() } let bs = read_to_vector("data/4.txt"); - let mut h: Vec = vec![]; - for i in 0..=255 { + let mut h: Vec = vec![]; + for i in 32..=127 { for b in bs.iter() { - h.push(HexBytes::xor_byte(b, i)); + h.push(Bytes::xor_byte(b, i)); } } h.sort_by(|a, b| a.ascii_score().partial_cmp(&b.ascii_score()).unwrap()); - let r = h[h.len() - 1].to_utf8_string(); + let r = h[h.len() - 1].to_utf8(); println!("[okay] Challenge 4: {}", r.trim()); } pub fn challenge5() { - let msg = HexBytes::from_str( + let msg = Bytes::from_utf8( "Burning 'em, if you ain't quick and nimble\nI go crazy when I hear a cymbal", ); - let key = HexBytes::from_str("ICE"); - let enc = HexBytes::xor_cycle(&msg, &key); - let exp = HexBytes::from_hex_str("0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f"); + let key = Bytes::from_utf8("ICE"); + let enc = Bytes::xor_cycle(&msg, &key); + let exp = Bytes::from_hex("0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f"); if enc == exp { - println!("[okay] Challenge 5: {}", enc.to_hex_string()); + println!("[okay] Challenge 5: {}", enc.to_hex()); } else { println!("[fail] Challenge 5") } } pub fn challenge6() { + // next: base64bytes to hexbytes println!("[open] Challenge 6: xxx"); }