Finish challenge 29 - I didn't find it trivial tbh

This commit is contained in:
2022-08-19 18:39:29 -04:00
parent 65fe5a7f96
commit c12b1c45a6
4 changed files with 139 additions and 71 deletions

View File

@@ -14,7 +14,7 @@ mod sha1;
mod utils;
fn main() {
const RUN_ALL: bool = true;
const RUN_ALL: bool = false;
if RUN_ALL {
set1::challenge1();
set1::challenge2();
@@ -45,7 +45,8 @@ fn main() {
set4::challenge27();
set4::challenge28();
set4::challenge29();
set4::challenge30();
} else {
set4::challenge29();
set4::challenge30();
}
}

View File

@@ -1,98 +0,0 @@
import binascii
import string
from typing import List, Dict, Set
def load_ciphers() -> List[bytes]:
with open("data/19_enc.txt", "r") as f:
r = [binascii.a2b_base64(line) for line in f]
return r
def xor_lookup_set(letters: str) -> Dict[int, Set[str]]:
d = {i: set() for i in range(256)}
for c1 in letters:
for c2 in letters:
xored = ord(c1) ^ ord(c2)
d[xored].add(c1)
d[xored].add(c2)
return d
def attack(ciphers: List[bytes]) -> List[List[str]]:
""" Find out possible characters for each cipher pair and hope that there
is only one possible character. If no character was found add '?' and
if more than one was found add '^'. """
ciphers_len = len(ciphers)
deciphered = [[] for _ in range(ciphers_len)]
max_cipher_len = max(map(len, ciphers))
for byte_index in range(0, max_cipher_len):
# Certain bytes only work with certain letters (found empirically).
if byte_index == 10:
LETTERS = string.ascii_letters + " _-.,;:'"
elif byte_index == 20:
LETTERS = string.ascii_letters + " _-.,;:?"
else:
LETTERS = string.ascii_letters + " _-.,;:"
lookup = xor_lookup_set(LETTERS)
target_bytes = [cipher[byte_index]
if len(cipher) > byte_index
else None
for cipher in ciphers]
possible_chars = [set(LETTERS) for _ in range(ciphers_len)]
for i in range(ciphers_len):
for j in range(i, ciphers_len):
if target_bytes[i] is None or target_bytes[j] is None:
continue
xored = target_bytes[i] ^ target_bytes[j]
chars = lookup[xored]
possible_chars[i] &= chars
possible_chars[j] &= chars
for cipher_index in range(ciphers_len):
if len(ciphers[cipher_index]) <= byte_index:
continue
chars = list(possible_chars[cipher_index])
match len(chars):
case 0:
# print(f"No chars for {cipher_index=} {byte_index=}")
deciphered[cipher_index].append('?')
case 1:
deciphered[cipher_index].append(chars[0].lower())
case 2:
if chars[0].lower() == chars[1].lower():
deciphered[cipher_index].append(chars[0].lower())
else:
# print(f"Two {chars=} {cipher_index=} {byte_index=}")
deciphered[cipher_index].append('^')
case _:
# print(f"Too many {chars=} {cipher_index=} {byte_index=}")
deciphered[cipher_index].append('^')
return deciphered
def manual(decrypts: List[List[str]]) -> List[bytes]:
""" Manually add guessed letters. """
decrypts[0][30] = 'y'
decrypts[2][30] = 'y'
decrypts[4][30] = 'e'
decrypts[4][32] = 'h'
decrypts[4][33] = 'e'
decrypts[4][34] = 'a'
decrypts[4][35] = 'd'
decrypts[6][30] = 'i'
decrypts[13][30] = ' '
decrypts[20][30] = ' '
decrypts[25][30] = 'n'
decrypts[28][30] = ' '
decrypts[29][30] = 't'
decrypts[37][30] = 'i'
decrypts = list(map(lambda l: "".join(l), decrypts))
return decrypts
if __name__ == "__main__":
ciphers = load_ciphers()
decrypts = manual(attack(ciphers))
for d in decrypts:
print(d)

View File

@@ -1,11 +1,9 @@
#![allow(arithmetic_overflow)]
use crate::{bytes::Bytes, cbc, ctr, parser, sha1, utils};
use crate::{bytes::Bytes, cbc, ctr, ecb, parser, sha1, utils};
pub fn challenge25() {
let cipher = utils::read_base64("data/25.txt");
let key = Bytes::from_utf8("YELLOW SUBMARINE");
let plaintext = crate::ecb::decrypt(&key, &cipher);
let plaintext = ecb::decrypt(&key, &cipher);
let key = Bytes::random(16);
let nonce: u64 = 0; // otherwise edit would require the nonce too?
@@ -32,8 +30,8 @@ pub fn challenge25() {
let ciphertext = ctr::encrypt(&key, nonce, &plaintext);
let newtext = vec![b'a'; ciphertext.len()];
let cipher_newtext = edit(&ciphertext, &key, 0, &newtext);
let keystream = crate::utils::xor(&newtext, &cipher_newtext.0);
let recovered_plaintext = Bytes(crate::utils::xor(&keystream, &ciphertext.0));
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");
@@ -140,50 +138,99 @@ pub fn challenge27() {
}
pub fn challenge28() {
let mut sha1 = sha1::Sha1::default();
let i1 = Bytes(vec![b'a'; 64]);
let e1 = Bytes::from_hex("0098ba824b5c16427bd7a1122a5a442a25ec644d");
let o1 = sha1::hash(&i1);
let o1 = sha1.hash(&i1);
assert_eq!(e1, o1);
sha1.reset();
let i2 = Bytes(vec![b'a'; 128]);
let e2 = Bytes::from_hex("ad5b3fdbcb526778c2839d2f151ea753995e26a0");
let o2 = sha1::hash(&i2);
let o2 = sha1.hash(&i2);
assert_eq!(e2, o2);
sha1.reset();
let i3 = Bytes(vec![b'a'; 3]);
let e3 = Bytes::from_hex("7e240de74fb1ed08fa08d38063f6a6a91462a815");
let o3 = sha1::hash(&i3);
let o3 = sha1.hash(&i3);
assert_eq!(e3, o3);
sha1.reset();
let i4 = Bytes(vec![]);
let e4 = Bytes::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709");
let o4 = sha1::hash(&i4);
let o4 = sha1.hash(&i4);
assert_eq!(e4, o4);
fn authenticate(message: &Bytes, key: &Bytes) -> Bytes {
// Write a function to authenticate a message under a secret key by using a
// secret-prefix MAC, which is simply:
// SHA1(key || message)
let mut c = vec![];
c.append(&mut key.0.to_vec());
c.append(&mut message.0.to_vec());
// how to concatenate better: https://stackoverflow.com/a/56490417
sha1::hash(&Bytes(c))
}
// 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 auth = authenticate(&message, &key);
let mac = sha1::authenticate(&message, &key);
message.flip_bit(2, 3);
let auth_tempered = authenticate(&message, &key);
assert!(auth != auth_tempered);
assert!(!sha1::verify(&message, &key, &mac));
println!("[okay] Challenge 28: implemented SHA-1");
}
pub fn challenge29() {
println!("[xxxx] Challenge 29: TBD");
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<u32> = 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() {
println!("[xxxx] Challenge 30: tbd");
}

View File

@@ -1,5 +1,5 @@
use crate::bytes::Bytes;
/// Sha1 implementation based on https://github.com/vog/sha1/blob/master/sha1.hpp
use crate::bytes::Bytes;
use std::num::Wrapping;
const STATE_LEN: usize = 5;
@@ -8,9 +8,10 @@ const BLOCK_BYTES: usize = BLOCK_INTS * 4; // 64
type Wu32 = Wrapping<u32>;
type Block = [Wu32; BLOCK_INTS];
#[derive(Debug)]
pub struct Sha1 {
h: [Wu32; STATE_LEN],
block_len: u64,
byte_len: u64,
}
impl Default for Sha1 {
@@ -24,11 +25,21 @@ impl Default for Sha1 {
Wrapping(0x10325476),
Wrapping(0xC3D2E1F0),
],
block_len: 0,
byte_len: 0,
}
}
}
fn bytes_to_block(bytes: &[u8]) -> Block {
assert_eq!(bytes.len(), BLOCK_BYTES);
// safety: unwraps work because BLOCK_BYTES length is asserted
let b: Vec<Wu32> = bytes
.chunks(4)
.map(|c| Wrapping(u32::from_be_bytes(c.try_into().unwrap())))
.collect();
b.try_into().unwrap()
}
fn rol(value: Wu32, bits: usize) -> Wu32 {
return (value << bits) | (value >> (32 - bits));
}
@@ -71,10 +82,17 @@ fn r4(block: &mut Block, v: Wu32, w: &mut Wu32, x: Wu32, y: Wu32, z: &mut Wu32,
impl Sha1 {
#[inline]
fn _reset(&mut self) {
pub fn reset(&mut self) {
*self = Default::default();
}
pub fn fix(&mut self, fixate: [u32; STATE_LEN], byte_len: u64) {
for i in 0..5 {
self.h[i] = Wrapping(fixate[i]);
}
self.byte_len = byte_len;
}
fn transform(&mut self, block: &mut Block) {
/* Copy digest[] to working vars */
let mut a = self.h[0];
@@ -173,62 +191,64 @@ impl Sha1 {
self.h[4] += e;
/* Count the number of transformations */
self.block_len += 1;
self.byte_len += BLOCK_BYTES as u64;
}
fn update(&mut self, bytes: &[u8]) {
if bytes.len() % BLOCK_BYTES != 0 {
panic!("we only support buffers that are a multiples of 64 atm")
}
assert_eq!(bytes.len() % BLOCK_BYTES, 0);
let mut block = bytes_to_block(bytes);
self.transform(&mut block);
}
fn finalize(&mut self, last_chunk: Vec<u8>) -> Bytes {
let mut buffer = last_chunk;
let total_bits: u64 = (self.block_len * (BLOCK_BYTES as u64) + buffer.len() as u64) * 8;
pub fn get_padding(&self, bytes: &Vec<u8>) -> Vec<u8> {
let mut padding = vec![];
buffer.push(0x80);
// append 0x80 to the message
padding.push(0x80);
let orig_size = buffer.len();
while buffer.len() < BLOCK_BYTES {
buffer.push(0x0);
// append 0 ≤ k < 64 bytes so that message.len() ≡ 56 (mod 64)
while (bytes.len() + padding.len()) % BLOCK_BYTES != (BLOCK_BYTES - 8) {
padding.push(0x0);
}
let mut block = bytes_to_block(&buffer);
if orig_size > (BLOCK_BYTES - 8) as usize {
self.transform(&mut block);
for i in 0..(BLOCK_INTS - 2) {
block[i] = Wrapping(0);
// append the original message length as a 64-bit big-endian integer
let bits: u64 = (self.byte_len + bytes.len() as u64) as u64 * 8;
padding.append(&mut ((bits >> 32) as u32).to_be_bytes().to_vec());
padding.append(&mut (bits as u32).to_be_bytes().to_vec());
padding
}
pub fn hash(&mut self, bytes: &Bytes) -> Bytes {
let mut final_bytes = vec![];
for bytes in bytes.0.chunks(BLOCK_BYTES) {
if bytes.len() == BLOCK_BYTES {
self.update(&bytes);
} else {
final_bytes = bytes.to_vec();
}
}
block[BLOCK_INTS - 1] = Wrapping(total_bits as u32);
block[BLOCK_INTS - 2] = Wrapping((total_bits >> 32) as u32);
self.transform(&mut block);
let mut padding = self.get_padding(&final_bytes);
final_bytes.append(&mut padding);
for bytes in final_bytes.chunks(BLOCK_BYTES) {
self.update(&bytes);
}
Bytes(self.h.iter().map(|i| i.0.to_be_bytes()).flatten().collect())
}
}
fn bytes_to_block(bytes: &[u8]) -> Block {
assert_eq!(bytes.len(), BLOCK_BYTES);
// safety: unwraps work because BLOCK_BYTES length is asserted
let b: Vec<Wu32> = bytes
.chunks(4)
.map(|c| Wrapping(u32::from_be_bytes(c.try_into().unwrap())))
.collect();
b.try_into().unwrap()
pub fn authenticate(message: &Bytes, key: &Bytes) -> Bytes {
// Write a function to authenticate a message under a secret key by using a
// secret-prefix MAC, which is simply:
// SHA1(key || message)
let mut c = vec![];
c.append(&mut key.0.to_vec());
c.append(&mut message.0.to_vec());
// how to concatenate better: https://stackoverflow.com/a/56490417
let mut sha1 = Sha1::default();
let r = sha1.hash(&Bytes(c));
r
}
pub fn hash(bytes: &Bytes) -> Bytes {
let mut s = Sha1::default();
let mut last_chunk = vec![];
for bytes in bytes.0.chunks(BLOCK_BYTES) {
if bytes.len() == BLOCK_BYTES {
s.update(&bytes);
} else {
last_chunk = bytes.to_vec();
}
}
s.finalize(last_chunk)
pub fn verify(message: &Bytes, key: &Bytes, mac: &Bytes) -> bool {
return authenticate(&message, &key) == *mac;
}