Finish challenge 29 - I didn't find it trivial tbh
This commit is contained in:
@@ -14,7 +14,7 @@ mod sha1;
|
|||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
const RUN_ALL: bool = true;
|
const RUN_ALL: bool = false;
|
||||||
if RUN_ALL {
|
if RUN_ALL {
|
||||||
set1::challenge1();
|
set1::challenge1();
|
||||||
set1::challenge2();
|
set1::challenge2();
|
||||||
@@ -45,7 +45,8 @@ fn main() {
|
|||||||
set4::challenge27();
|
set4::challenge27();
|
||||||
set4::challenge28();
|
set4::challenge28();
|
||||||
set4::challenge29();
|
set4::challenge29();
|
||||||
|
set4::challenge30();
|
||||||
} else {
|
} else {
|
||||||
set4::challenge29();
|
set4::challenge30();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
src/set4.rs
97
src/set4.rs
@@ -1,11 +1,9 @@
|
|||||||
#![allow(arithmetic_overflow)]
|
use crate::{bytes::Bytes, cbc, ctr, ecb, parser, sha1, utils};
|
||||||
|
|
||||||
use crate::{bytes::Bytes, cbc, ctr, parser, sha1, utils};
|
|
||||||
|
|
||||||
pub fn challenge25() {
|
pub fn challenge25() {
|
||||||
let cipher = utils::read_base64("data/25.txt");
|
let cipher = utils::read_base64("data/25.txt");
|
||||||
let key = Bytes::from_utf8("YELLOW SUBMARINE");
|
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 key = Bytes::random(16);
|
||||||
let nonce: u64 = 0; // otherwise edit would require the nonce too?
|
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 ciphertext = ctr::encrypt(&key, nonce, &plaintext);
|
||||||
let newtext = vec![b'a'; ciphertext.len()];
|
let newtext = vec![b'a'; ciphertext.len()];
|
||||||
let cipher_newtext = edit(&ciphertext, &key, 0, &newtext);
|
let cipher_newtext = edit(&ciphertext, &key, 0, &newtext);
|
||||||
let keystream = crate::utils::xor(&newtext, &cipher_newtext.0);
|
let keystream = utils::xor(&newtext, &cipher_newtext.0);
|
||||||
let recovered_plaintext = Bytes(crate::utils::xor(&keystream, &ciphertext.0));
|
let recovered_plaintext = Bytes(utils::xor(&keystream, &ciphertext.0));
|
||||||
assert_eq!(plaintext, recovered_plaintext);
|
assert_eq!(plaintext, recovered_plaintext);
|
||||||
|
|
||||||
println!("[okay] Challenge 25: recovered AES CTR plaintext via edit");
|
println!("[okay] Challenge 25: recovered AES CTR plaintext via edit");
|
||||||
@@ -140,50 +138,99 @@ pub fn challenge27() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn challenge28() {
|
pub fn challenge28() {
|
||||||
|
let mut sha1 = sha1::Sha1::default();
|
||||||
let i1 = Bytes(vec![b'a'; 64]);
|
let i1 = Bytes(vec![b'a'; 64]);
|
||||||
let e1 = Bytes::from_hex("0098ba824b5c16427bd7a1122a5a442a25ec644d");
|
let e1 = Bytes::from_hex("0098ba824b5c16427bd7a1122a5a442a25ec644d");
|
||||||
let o1 = sha1::hash(&i1);
|
let o1 = sha1.hash(&i1);
|
||||||
assert_eq!(e1, o1);
|
assert_eq!(e1, o1);
|
||||||
|
|
||||||
|
sha1.reset();
|
||||||
let i2 = Bytes(vec![b'a'; 128]);
|
let i2 = Bytes(vec![b'a'; 128]);
|
||||||
let e2 = Bytes::from_hex("ad5b3fdbcb526778c2839d2f151ea753995e26a0");
|
let e2 = Bytes::from_hex("ad5b3fdbcb526778c2839d2f151ea753995e26a0");
|
||||||
let o2 = sha1::hash(&i2);
|
let o2 = sha1.hash(&i2);
|
||||||
assert_eq!(e2, o2);
|
assert_eq!(e2, o2);
|
||||||
|
|
||||||
|
sha1.reset();
|
||||||
let i3 = Bytes(vec![b'a'; 3]);
|
let i3 = Bytes(vec![b'a'; 3]);
|
||||||
let e3 = Bytes::from_hex("7e240de74fb1ed08fa08d38063f6a6a91462a815");
|
let e3 = Bytes::from_hex("7e240de74fb1ed08fa08d38063f6a6a91462a815");
|
||||||
let o3 = sha1::hash(&i3);
|
let o3 = sha1.hash(&i3);
|
||||||
assert_eq!(e3, o3);
|
assert_eq!(e3, o3);
|
||||||
|
|
||||||
|
sha1.reset();
|
||||||
let i4 = Bytes(vec![]);
|
let i4 = Bytes(vec![]);
|
||||||
let e4 = Bytes::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
let e4 = Bytes::from_hex("da39a3ee5e6b4b0d3255bfef95601890afd80709");
|
||||||
let o4 = sha1::hash(&i4);
|
let o4 = sha1.hash(&i4);
|
||||||
assert_eq!(e4, o4);
|
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
|
// 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
|
// you've produced, and that you can't produce a new MAC without knowing the
|
||||||
// secret key.
|
// secret key.
|
||||||
let mut message = Bytes::from_utf8("love, love, love");
|
let mut message = Bytes::from_utf8("love, love, love");
|
||||||
let key = Bytes::from_utf8("kisses!");
|
let key = Bytes::from_utf8("kisses!");
|
||||||
let auth = authenticate(&message, &key);
|
let mac = sha1::authenticate(&message, &key);
|
||||||
message.flip_bit(2, 3);
|
message.flip_bit(2, 3);
|
||||||
let auth_tempered = authenticate(&message, &key);
|
assert!(!sha1::verify(&message, &key, &mac));
|
||||||
assert!(auth != auth_tempered);
|
|
||||||
|
|
||||||
println!("[okay] Challenge 28: implemented SHA-1");
|
println!("[okay] Challenge 28: implemented SHA-1");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn challenge29() {
|
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");
|
||||||
}
|
}
|
||||||
|
|||||||
108
src/sha1.rs
108
src/sha1.rs
@@ -1,5 +1,5 @@
|
|||||||
use crate::bytes::Bytes;
|
|
||||||
/// Sha1 implementation based on https://github.com/vog/sha1/blob/master/sha1.hpp
|
/// Sha1 implementation based on https://github.com/vog/sha1/blob/master/sha1.hpp
|
||||||
|
use crate::bytes::Bytes;
|
||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
|
|
||||||
const STATE_LEN: usize = 5;
|
const STATE_LEN: usize = 5;
|
||||||
@@ -8,9 +8,10 @@ const BLOCK_BYTES: usize = BLOCK_INTS * 4; // 64
|
|||||||
type Wu32 = Wrapping<u32>;
|
type Wu32 = Wrapping<u32>;
|
||||||
type Block = [Wu32; BLOCK_INTS];
|
type Block = [Wu32; BLOCK_INTS];
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Sha1 {
|
pub struct Sha1 {
|
||||||
h: [Wu32; STATE_LEN],
|
h: [Wu32; STATE_LEN],
|
||||||
block_len: u64,
|
byte_len: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Sha1 {
|
impl Default for Sha1 {
|
||||||
@@ -24,11 +25,21 @@ impl Default for Sha1 {
|
|||||||
Wrapping(0x10325476),
|
Wrapping(0x10325476),
|
||||||
Wrapping(0xC3D2E1F0),
|
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 {
|
fn rol(value: Wu32, bits: usize) -> Wu32 {
|
||||||
return (value << bits) | (value >> (32 - bits));
|
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 {
|
impl Sha1 {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn _reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
*self = Default::default();
|
*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) {
|
fn transform(&mut self, block: &mut Block) {
|
||||||
/* Copy digest[] to working vars */
|
/* Copy digest[] to working vars */
|
||||||
let mut a = self.h[0];
|
let mut a = self.h[0];
|
||||||
@@ -173,62 +191,64 @@ impl Sha1 {
|
|||||||
self.h[4] += e;
|
self.h[4] += e;
|
||||||
|
|
||||||
/* Count the number of transformations */
|
/* Count the number of transformations */
|
||||||
self.block_len += 1;
|
self.byte_len += BLOCK_BYTES as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(&mut self, bytes: &[u8]) {
|
fn update(&mut self, bytes: &[u8]) {
|
||||||
if bytes.len() % BLOCK_BYTES != 0 {
|
assert_eq!(bytes.len() % BLOCK_BYTES, 0);
|
||||||
panic!("we only support buffers that are a multiples of 64 atm")
|
|
||||||
}
|
|
||||||
let mut block = bytes_to_block(bytes);
|
let mut block = bytes_to_block(bytes);
|
||||||
self.transform(&mut block);
|
self.transform(&mut block);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finalize(&mut self, last_chunk: Vec<u8>) -> Bytes {
|
pub fn get_padding(&self, bytes: &Vec<u8>) -> Vec<u8> {
|
||||||
let mut buffer = last_chunk;
|
let mut padding = vec![];
|
||||||
let total_bits: u64 = (self.block_len * (BLOCK_BYTES as u64) + buffer.len() as u64) * 8;
|
|
||||||
|
|
||||||
buffer.push(0x80);
|
// append 0x80 to the message
|
||||||
|
padding.push(0x80);
|
||||||
|
|
||||||
let orig_size = buffer.len();
|
// append 0 ≤ k < 64 bytes so that message.len() ≡ 56 (mod 64)
|
||||||
while buffer.len() < BLOCK_BYTES {
|
while (bytes.len() + padding.len()) % BLOCK_BYTES != (BLOCK_BYTES - 8) {
|
||||||
buffer.push(0x0);
|
padding.push(0x0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut block = bytes_to_block(&buffer);
|
// append the original message length as a 64-bit big-endian integer
|
||||||
if orig_size > (BLOCK_BYTES - 8) as usize {
|
let bits: u64 = (self.byte_len + bytes.len() as u64) as u64 * 8;
|
||||||
self.transform(&mut block);
|
padding.append(&mut ((bits >> 32) as u32).to_be_bytes().to_vec());
|
||||||
for i in 0..(BLOCK_INTS - 2) {
|
padding.append(&mut (bits as u32).to_be_bytes().to_vec());
|
||||||
block[i] = Wrapping(0);
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut padding = self.get_padding(&final_bytes);
|
||||||
block[BLOCK_INTS - 1] = Wrapping(total_bits as u32);
|
final_bytes.append(&mut padding);
|
||||||
block[BLOCK_INTS - 2] = Wrapping((total_bits >> 32) as u32);
|
for bytes in final_bytes.chunks(BLOCK_BYTES) {
|
||||||
self.transform(&mut block);
|
self.update(&bytes);
|
||||||
|
}
|
||||||
Bytes(self.h.iter().map(|i| i.0.to_be_bytes()).flatten().collect())
|
Bytes(self.h.iter().map(|i| i.0.to_be_bytes()).flatten().collect())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bytes_to_block(bytes: &[u8]) -> Block {
|
pub fn authenticate(message: &Bytes, key: &Bytes) -> Bytes {
|
||||||
assert_eq!(bytes.len(), BLOCK_BYTES);
|
// Write a function to authenticate a message under a secret key by using a
|
||||||
// safety: unwraps work because BLOCK_BYTES length is asserted
|
// secret-prefix MAC, which is simply:
|
||||||
let b: Vec<Wu32> = bytes
|
// SHA1(key || message)
|
||||||
.chunks(4)
|
let mut c = vec![];
|
||||||
.map(|c| Wrapping(u32::from_be_bytes(c.try_into().unwrap())))
|
c.append(&mut key.0.to_vec());
|
||||||
.collect();
|
c.append(&mut message.0.to_vec());
|
||||||
b.try_into().unwrap()
|
// 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 {
|
pub fn verify(message: &Bytes, key: &Bytes, mac: &Bytes) -> bool {
|
||||||
let mut s = Sha1::default();
|
return authenticate(&message, &key) == *mac;
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user