Implement challenge 31 to recover HMAC-SHA1 via timing attack
parent
bac75acd2c
commit
75d4b97524
|
@ -1,3 +1,10 @@
|
|||
#![warn(clippy::pedantic)]
|
||||
#![allow(clippy::cast_precision_loss)]
|
||||
#![allow(clippy::items_after_statements)]
|
||||
#![allow(clippy::many_single_char_names)]
|
||||
#![allow(clippy::module_name_repetitions)]
|
||||
#![allow(clippy::cast_possible_truncation)]
|
||||
#![allow(clippy::unnested_or_patterns)]
|
||||
mod bytes;
|
||||
mod bytes_base64;
|
||||
mod cbc;
|
||||
|
@ -15,7 +22,7 @@ mod sha1;
|
|||
mod utils;
|
||||
|
||||
fn main() {
|
||||
const RUN_ALL: bool = true;
|
||||
const RUN_ALL: bool = false;
|
||||
if RUN_ALL {
|
||||
set1::challenge1();
|
||||
set1::challenge2();
|
||||
|
|
65
src/set4.rs
65
src/set4.rs
|
@ -1,3 +1,6 @@
|
|||
use std::path::Path;
|
||||
use std::fs;
|
||||
use std::{thread, time};
|
||||
use crate::{bytes::Bytes, cbc, ctr, ecb, md4, parser, sha1, utils};
|
||||
|
||||
pub fn challenge25() {
|
||||
|
@ -299,5 +302,65 @@ pub fn challenge30() {
|
|||
}
|
||||
|
||||
pub fn challenge31() {
|
||||
println!("[xxxx] Challenge 31: tbd");
|
||||
fn insecure_compare(a: &[u8], b: &[u8]) -> bool {
|
||||
// Write a function, call it "insecure_compare", that implements the == operation by doing
|
||||
// byte-at-a-time comparisons with early exit (ie, return false at the first non-matching
|
||||
// byte).
|
||||
let delay = time::Duration::from_millis(8);
|
||||
if a.len() != b.len() {
|
||||
return false;
|
||||
}
|
||||
for (a, b) in a.iter().zip(b.iter()) {
|
||||
if a != b {
|
||||
return false;
|
||||
}
|
||||
// In the loop for "insecure_compare", add a 50ms sleep (sleep 50ms after each byte).
|
||||
thread::sleep(delay);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn verify(file: &Path, signature: &[u8]) -> bool {
|
||||
// Have the server generate an HMAC key, and then verify that the "signature" on incoming
|
||||
// requests is valid for "file", using the "==" operator to compare the valid MAC for a
|
||||
// file with the "signature" parameter (in other words, verify the HMAC the way any normal
|
||||
// programmer would verify it).
|
||||
let key = Bytes::from_utf8("sosecretbb");
|
||||
let contents = fs::read_to_string(file);
|
||||
assert!(contents.is_ok(), "Could not read: {}", file.display());
|
||||
let contents = Bytes(contents.unwrap().as_bytes().to_vec());
|
||||
insecure_compare(&sha1::hmac_sha1(&key, &contents).0, signature)
|
||||
}
|
||||
|
||||
fn attack(file: &Path) -> Bytes {
|
||||
const BLOCK_SIZE: usize = 20;
|
||||
let mut sig = vec![0x0; BLOCK_SIZE];
|
||||
for i in 0..BLOCK_SIZE {
|
||||
let mut max_tuple: (u128, u8) = (u128::MIN, 0);
|
||||
for c in 0_u8..=255_u8 {
|
||||
let now = time::Instant::now();
|
||||
sig[i] = c;
|
||||
verify(file, &sig);
|
||||
let elapsed = now.elapsed().as_micros();
|
||||
if elapsed > max_tuple.0 {
|
||||
max_tuple = (elapsed, c);
|
||||
}
|
||||
}
|
||||
sig[i] = max_tuple.1;
|
||||
}
|
||||
Bytes(sig)
|
||||
}
|
||||
|
||||
let key = Bytes::from_utf8("YELLOW SUBMARINE");
|
||||
let message = Bytes::from_utf8("Attact at dawn after tomorrow when it's cold inside.");
|
||||
assert_eq!(sha1::hmac_sha1(&key, &message), Bytes::from_hex("8232f3d05afb6bce7e09fe764885cc158e435e36"));
|
||||
|
||||
let path = Path::new("data/12.txt");
|
||||
let expected_sig = Bytes::from_hex("62f4527ea6cb716d0ad1ca0fc69135a49bc2d138");
|
||||
assert!(verify(path, &expected_sig.0), "Invalid signature");
|
||||
|
||||
let signature = attack(path);
|
||||
assert_eq!(expected_sig.0, signature.0, "Recovery of HMAC-SHA1 failed");
|
||||
|
||||
println!("[okay] Challenge 31: recoverd HMAC-SHA1 via timing attack");
|
||||
}
|
||||
|
|
30
src/sha1.rs
30
src/sha1.rs
|
@ -264,3 +264,33 @@ pub fn authenticate(message: &Bytes, key: &Bytes) -> Bytes {
|
|||
pub fn verify(message: &Bytes, key: &Bytes, mac: &Bytes) -> bool {
|
||||
authenticate(message, key) == *mac
|
||||
}
|
||||
|
||||
pub fn hmac_sha1(key: &Bytes, message: &Bytes) -> Bytes {
|
||||
fn hash(message: Vec<u8>) -> Vec<u8> {
|
||||
let mut sha1 = Sha1::default();
|
||||
sha1.hash(&Bytes(message)).0
|
||||
}
|
||||
|
||||
fn compute_block_sized_key(key: &Bytes, block_size: usize) -> Bytes {
|
||||
let mut key = key.0.clone();
|
||||
if key.len() > block_size {
|
||||
key = hash(key);
|
||||
}
|
||||
|
||||
while key.len() < block_size {
|
||||
key.push(0x0);
|
||||
}
|
||||
Bytes(key)
|
||||
}
|
||||
|
||||
const BLOCK_SIZE: usize = 64;
|
||||
let block_sized_key = compute_block_sized_key(key, BLOCK_SIZE);
|
||||
let mut o_key_pad = Bytes::xor(&block_sized_key, &Bytes(vec![0x5c; BLOCK_SIZE])).0;
|
||||
let mut i_key_pad = Bytes::xor(&block_sized_key, &Bytes(vec![0x36; BLOCK_SIZE])).0;
|
||||
|
||||
// hash(o_key_pad ∥ hash(i_key_pad ∥ message))
|
||||
i_key_pad.append(&mut message.0.clone());
|
||||
let mut inner_hashed = hash(i_key_pad);
|
||||
o_key_pad.append(&mut inner_hashed);
|
||||
Bytes(hash(o_key_pad))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue