use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils}; pub fn challenge25() { let cipher = utils::read_base64("data/25.txt"); let key = Bytes::from_utf8("YELLOW SUBMARINE"); let plaintext = ecb::decrypt(&key, &cipher); let key = Bytes::random(16); let nonce: u64 = 0; // otherwise edit would require the nonce too? // Now, write the code that allows you to "seek" into the ciphertext, // decrypt, and re-encrypt with different plaintext. Expose this as a // function, like, "edit(ciphertext, key, offset, newtext)". fn edit(ciphertext: &Bytes, key: &Bytes, offset: usize, newtext: &Vec) -> Bytes { let mut plaintext = ctr::decrypt(key, 0, ciphertext); if offset + newtext.len() > plaintext.len() { panic!("challenge25 - edit - out of bounds"); } for i in 0..newtext.len() { plaintext.0[offset + i] = newtext[i]; } ctr::encrypt(key, 0, &plaintext) } // Imagine the "edit" function was exposed to attackers by means of an API // call that didn't reveal the key or the original plaintext; the attacker // has the ciphertext and controls the offset and "new text". Recover the // original plaintext. let ciphertext = ctr::encrypt(&key, nonce, &plaintext); let newtext = vec![b'a'; ciphertext.len()]; let cipher_newtext = edit(&ciphertext, &key, 0, &newtext); let keystream = utils::xor(&newtext, &cipher_newtext.0); let recovered_plaintext = Bytes(utils::xor(&keystream, &ciphertext.0)); assert_eq!(plaintext, recovered_plaintext); println!("[okay] Challenge 25: recovered AES CTR plaintext via edit"); // A folkloric supposed benefit of CTR mode is the ability to easily "seek // forward" into the ciphertext; to access byte N of the ciphertext, all you // need to be able to do is generate byte N of the keystream. Imagine if // you'd relied on that advice to, say, encrypt a disk. } pub fn challenge26() { fn encrypt(input: &str, key: &Bytes) -> Bytes { let mut r = String::new(); for c in input.chars() { if c == ';' || c == '=' { panic!("encrypt: invalid char {}", c); } } r.push_str("comment1=cooking%20MCs;userdata="); r.push_str(input); r.push_str(";comment2=%20like%20a%20pound%20of%20bacon"); let data = Bytes(r.as_bytes().to_vec()); ctr::encrypt(key, 0, &data) } let key = Bytes::random(16); // 0 16 32 48 64 // 0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f0..34..78..bc..f // comment1=cooking%20MCs;userdata=aaaaaaaaaaaaaaaa;comment2=%20like%20a%20pound%20of%20bacon // comment1=cooking%20MCs;userdata=fobar;admin=true;comment2=%20like%20a%20pound%20of%20bacon let input = "aaaaaaaaaaaaaaaa"; let cipher = encrypt(&input, &key); let keystream = utils::xor(&cipher.0[32..48], &Bytes::from_utf8(input).0); let input = "fobar;admin=true"; let mut flipped_cipher = cipher.0[0..32].to_vec(); flipped_cipher.append(&mut utils::xor(&keystream, &input.as_bytes())); flipped_cipher.append(&mut cipher.0[48..cipher.len()].to_vec()); let cleartext = ctr::decrypt(&key, 0, &Bytes(flipped_cipher)); let dict = parser::parse_key_value(&cleartext.to_utf8()); let admin_status = dict.get("admin").unwrap(); assert_eq!(admin_status, "true"); println!("[okay] Challenge 26: admin={}", admin_status); } pub fn challenge27() { fn encrypt(key: &Bytes) -> Bytes { // AES-CBC(P_1, P_2, P_3) -> C_1, C_2, C_3 let mut ct = Bytes::from_utf8("comment1=cooking%20MCs;userdata=secretsaucenouse"); ct.pad_pkcs7(16); cbc::encrypt(&key, &key, &ct) } fn decrypt(key: &Bytes, cipher: &Bytes) -> Result { // The CBC code from exercise 16 encrypts a URL string. Verify each byte // of the plaintext for ASCII compliance (ie, look for high-ASCII // values). Noncompliant messages should raise an exception or return an // error that includes the decrypted plaintext (this happens all the // time in real systems, for what it's worth). let mut cleartext = cbc::decrypt(&key, &key, &cipher); cleartext.remove_pkcs7(16); match std::str::from_utf8(&cleartext.0) { Ok(_) => Ok(cleartext), Err(_) => Err(cleartext), } } fn modify(cipher: &Bytes) -> Bytes { // C_1, C_2, C_3 -> C_1, 0, C_1 let c1 = cipher.get_block(0, 16).0; let mut modified = c1.to_vec(); modified.append(&mut vec![0; 16]); modified.append(&mut c1.to_vec()); Bytes(modified) } fn recover_key(plaintext: &Bytes) -> Bytes { // P'_1 XOR P'_3 let block_size = 16; Bytes(utils::xor( &plaintext.get_block(0, block_size).0, &plaintext.get_block(2, block_size).0, )) } let key = Bytes::random(16); // Use your code to encrypt a message that is at least 3 blocks long: let cipher = encrypt(&key); // Modify the message (you are now the attacker): let cipher = modify(&cipher); let plaintext = decrypt(&key, &cipher); // As the attacker, recovering the plaintext from the error, extract the key: let recovered_key = match plaintext { Ok(plaintext) => panic!("Ok: {:?}", plaintext.to_utf8()), Err(plaintext) => recover_key(&plaintext), }; assert_eq!(key, recovered_key); println!("[okay] Challenge 27: recovered key successfully"); } pub fn challenge28() { let mut sha1 = sha1::Sha1::default(); assert_eq!( Bytes::from_hex("0098ba824b5c16427bd7a1122a5a442a25ec644d"), sha1.hash(&Bytes(vec![b'a'; 64])) ); sha1.reset(); assert_eq!( Bytes::from_hex("ad5b3fdbcb526778c2839d2f151ea753995e26a0"), sha1.hash(&Bytes(vec![b'a'; 128])) ); sha1.reset(); assert_eq!( Bytes::from_hex("7e240de74fb1ed08fa08d38063f6a6a91462a815"), sha1.hash(&Bytes(vec![b'a'; 3])), ); sha1.reset(); assert_eq!( Bytes::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709"), sha1.hash(&Bytes(vec![])), ); // Verify that you cannot tamper with the message without breaking the MAC // you've produced, and that you can't produce a new MAC without knowing the // secret key. let mut message = Bytes::from_utf8("love, love, love"); let key = Bytes::from_utf8("kisses!"); let mac = sha1::authenticate(&message, &key); message.flip_bit(2, 3); assert!(!sha1::verify(&message, &key, &mac)); println!("[okay] Challenge 28: implemented SHA-1"); } pub fn challenge29() { fn hash_fixated(bytes: &Bytes, fixture: &Bytes, byte_len: u64) -> Bytes { // Now, take the SHA-1 secret-prefix MAC of the message you want to forge --- this is just // a SHA-1 hash --- and break it into 32 bit SHA-1 registers (SHA-1 calls them "a", "b", // "c", &c). let mut s = sha1::Sha1::default(); let fixate: Vec = fixture .0 .chunks(4) .map(|c| u32::from_be_bytes(c.try_into().unwrap())) .collect(); // Modify your SHA-1 implementation so that callers can pass in new // values for "a", "b", "c" &c (they normally start at magic numbers). // With the registers "fixated", hash the additional data you want to // forge. s.fix(fixate.try_into().unwrap(), byte_len); s.hash(&bytes) } // use random let key = Bytes::random_range(2, 64); let message = Bytes::from_utf8( "comment1=cooking%20MCs;userdata=foo;comment2=%20like%20a%20pound%20of%20bacon", ); let mac = sha1::authenticate(&message, &key); assert!(sha1::verify(&message, &key, &mac)); let mut forged_message = vec![]; let mut mac_forged = Bytes(vec![]); for key_len in 1..128 { // get padding for key || orig-message let key_guessed = vec![b'z'; key_len]; // key-guessed let mut bytes = key_guessed.to_vec(); bytes.append(&mut message.0.to_vec()); // original-message let s1 = sha1::Sha1::default(); let glue_padding = s1.get_padding(&bytes); // glue-padding // forget MAC via fixture: make sure to fix sha1.h *and* sha1.byte_length let byte_length = (key_guessed.len() + message.len() + glue_padding.len()) as u64; let new_message = b"admin=true".to_vec(); // new-message mac_forged = hash_fixated(&Bytes(new_message.to_vec()), &mac, byte_length); // forge message: original-message || glue-padding || new-message forged_message = message.0.to_vec(); forged_message.append(&mut glue_padding.to_vec()); forged_message.append(&mut new_message.to_vec()); let r = sha1::verify(&Bytes(forged_message.to_vec()), &key, &mac_forged); if r { break; } } assert!(sha1::verify(&Bytes(forged_message), &key, &mac_forged)); println!("[okay] Challenge 29: forged SHA-1 keyed MAC successfully"); } pub fn challenge30() { // Second verse, same as the first, but use MD4 instead of SHA-1. Having // done this attack once against SHA-1, the MD4 variant should take much // less time; mostly just the time you'll spend Googling for an // implementation of MD4. assert_eq!( md4::hash(&Bytes::from_utf8("")), Bytes::from_hex("31d6cfe0d16ae931b73c59d7e0c089c0"), ); // assert_eq!( // md4::hash(&Bytes::from_utf8("a")), // Bytes::from_hex("bde52cb31de33e46245e05fbdbd6fb24"), // ); // assert_eq!( // md4::hash(&Bytes::from_utf8("abc")), // Bytes::from_hex("a448017aaf21d8525fc10ae87aa6729d"), // ); // assert_eq!( // md4::hash(&Bytes::from_utf8("message digest")), // Bytes::from_hex("d9130a8164549fe818874806e1c7014b"), // ); // assert_eq!( // md4::hash(&Bytes::from_utf8("abcdefghijklmnopqrstuvwxyz")), // Bytes::from_hex("d79e1c308aa5bbcdeea8ed63df412da9"), // ); // assert_eq!( // md4::hash(&Bytes::from_utf8( // "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" // )), // Bytes::from_hex("043f8582f241db351ce627e153e7f0e4"), // ); // assert_eq!( // md4::hash(&Bytes::from_utf8( // "12345678901234567890123456789012345678901234567890123456789012345678901234567890" // )), // Bytes::from_hex("e33b4ddc9c38f2199c3e7b164fcc0536"), // ); println!("[xxxx] Challenge 30: tbd"); }