156 lines
4.9 KiB
Rust
156 lines
4.9 KiB
Rust
|
|
// spec, раздел "Криптографическая реализация → Primitive layer → HMAC-SHA-256 integer спецификация"
|
|||
|
|
|
|||
|
|
use mt_crypto::{sha256_raw, Hash32};
|
|||
|
|
use zeroize::Zeroize;
|
|||
|
|
|
|||
|
|
const BLOCK_SIZE: usize = 64;
|
|||
|
|
const IPAD_BYTE: u8 = 0x36;
|
|||
|
|
const OPAD_BYTE: u8 = 0x5C;
|
|||
|
|
|
|||
|
|
pub fn hmac_sha256(key: &[u8], message: &[u8]) -> Hash32 {
|
|||
|
|
let mut key_block = [0u8; BLOCK_SIZE];
|
|||
|
|
if key.len() > BLOCK_SIZE {
|
|||
|
|
let mut reduced = sha256_raw(key);
|
|||
|
|
key_block[..32].copy_from_slice(&reduced);
|
|||
|
|
reduced.zeroize();
|
|||
|
|
} else {
|
|||
|
|
key_block[..key.len()].copy_from_slice(key);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let mut key_ipad = [0u8; BLOCK_SIZE];
|
|||
|
|
let mut key_opad = [0u8; BLOCK_SIZE];
|
|||
|
|
for i in 0..BLOCK_SIZE {
|
|||
|
|
key_ipad[i] = key_block[i] ^ IPAD_BYTE;
|
|||
|
|
key_opad[i] = key_block[i] ^ OPAD_BYTE;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let inner = sha256_concat(&key_ipad, message);
|
|||
|
|
let result = sha256_concat(&key_opad, &inner);
|
|||
|
|
|
|||
|
|
// Zeroize key-derived padded blocks (содержат key XOR pad byte —
|
|||
|
|
// partial key recovery возможна при leak).
|
|||
|
|
key_block.zeroize();
|
|||
|
|
key_ipad.zeroize();
|
|||
|
|
key_opad.zeroize();
|
|||
|
|
|
|||
|
|
result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// HMAC требует raw SHA-256 без domain separation (RFC 2104, RFC 4231).
|
|||
|
|
// Используется sha256_raw из mt-crypto, НЕ domain-separated hash().
|
|||
|
|
fn sha256_concat(a: &[u8], b: &[u8]) -> Hash32 {
|
|||
|
|
let mut combined = Vec::with_capacity(a.len() + b.len());
|
|||
|
|
combined.extend_from_slice(a);
|
|||
|
|
combined.extend_from_slice(b);
|
|||
|
|
let result = sha256_raw(&combined);
|
|||
|
|
combined.zeroize();
|
|||
|
|
result
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[cfg(test)]
|
|||
|
|
mod tests {
|
|||
|
|
use super::*;
|
|||
|
|
|
|||
|
|
fn hex(bytes: &[u8]) -> String {
|
|||
|
|
let mut s = String::with_capacity(bytes.len() * 2);
|
|||
|
|
for b in bytes {
|
|||
|
|
s.push_str(&format!("{b:02x}"));
|
|||
|
|
}
|
|||
|
|
s
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RFC 4231 §4.2 Test Case 1
|
|||
|
|
// Key = 0x0b repeated 20 times
|
|||
|
|
// Data = "Hi There"
|
|||
|
|
// HMAC-SHA-256 = b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7
|
|||
|
|
#[test]
|
|||
|
|
fn rfc4231_case_1() {
|
|||
|
|
let key = [0x0bu8; 20];
|
|||
|
|
let msg = b"Hi There";
|
|||
|
|
let got = hmac_sha256(&key, msg);
|
|||
|
|
assert_eq!(
|
|||
|
|
hex(&got),
|
|||
|
|
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RFC 4231 §4.3 Test Case 2
|
|||
|
|
// Key = "Jefe" (ASCII, 4 bytes)
|
|||
|
|
// Data = "what do ya want for nothing?"
|
|||
|
|
// HMAC-SHA-256 = 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843
|
|||
|
|
#[test]
|
|||
|
|
fn rfc4231_case_2() {
|
|||
|
|
let key = b"Jefe";
|
|||
|
|
let msg = b"what do ya want for nothing?";
|
|||
|
|
let got = hmac_sha256(key, msg);
|
|||
|
|
assert_eq!(
|
|||
|
|
hex(&got),
|
|||
|
|
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RFC 4231 §4.5 Test Case 4
|
|||
|
|
// Key = 0x0102030405060708090a0b0c0d0e0f10111213141516171819 (25 bytes)
|
|||
|
|
// Data = 0xcd repeated 50 times
|
|||
|
|
// HMAC-SHA-256 = 82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b
|
|||
|
|
#[test]
|
|||
|
|
fn rfc4231_case_4() {
|
|||
|
|
let key: [u8; 25] = [
|
|||
|
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
|
|||
|
|
0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
|
|||
|
|
];
|
|||
|
|
let msg = [0xcdu8; 50];
|
|||
|
|
let got = hmac_sha256(&key, &msg);
|
|||
|
|
assert_eq!(
|
|||
|
|
hex(&got),
|
|||
|
|
"82558a389a443c0ea4cc819899f2083a85f0faa3e578f8077a2e3ff46729665b"
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// RFC 4231 §4.7 Test Case 6
|
|||
|
|
// Key = 0xaa repeated 131 times (longer than block size B=64 → key = SHA-256(key))
|
|||
|
|
// Data = "Test Using Larger Than Block-Size Key - Hash Key First"
|
|||
|
|
// HMAC-SHA-256 = 60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54
|
|||
|
|
#[test]
|
|||
|
|
fn rfc4231_case_6_long_key_triggers_sha256_reduction() {
|
|||
|
|
let key = [0xaau8; 131];
|
|||
|
|
let msg = b"Test Using Larger Than Block-Size Key - Hash Key First";
|
|||
|
|
let got = hmac_sha256(&key, msg);
|
|||
|
|
assert_eq!(
|
|||
|
|
hex(&got),
|
|||
|
|
"60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54"
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn empty_message_does_not_panic() {
|
|||
|
|
let key = [0x0bu8; 20];
|
|||
|
|
let out = hmac_sha256(&key, &[]);
|
|||
|
|
assert_eq!(out.len(), 32);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn empty_key_does_not_panic() {
|
|||
|
|
let msg = b"anything";
|
|||
|
|
let out = hmac_sha256(&[], msg);
|
|||
|
|
assert_eq!(out.len(), 32);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn key_exactly_block_size_no_reduction_no_padding() {
|
|||
|
|
let key = [0x5au8; BLOCK_SIZE];
|
|||
|
|
let msg = b"exactly block size";
|
|||
|
|
let out = hmac_sha256(&key, msg);
|
|||
|
|
assert_eq!(out.len(), 32);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
#[test]
|
|||
|
|
fn determinism_identical_input_identical_output() {
|
|||
|
|
let key = b"deterministic-key";
|
|||
|
|
let msg = b"deterministic-message";
|
|||
|
|
let a = hmac_sha256(key, msg);
|
|||
|
|
let b = hmac_sha256(key, msg);
|
|||
|
|
assert_eq!(a, b);
|
|||
|
|
}
|
|||
|
|
}
|