Skip to main content

happy_cracking/crypto/
hmac.rs

1use anyhow::Result;
2use clap::Subcommand;
3use md5::Md5;
4use sha1::Sha1;
5use sha2::{Digest, Sha256, Sha512};
6
7#[derive(Subcommand)]
8pub enum HmacAction {
9    #[command(about = "HMAC-MD5")]
10    Md5 {
11        #[arg(help = "Input message")]
12        input: String,
13        #[arg(short, long, help = "HMAC key")]
14        key: String,
15    },
16    #[command(about = "HMAC-SHA1")]
17    Sha1 {
18        #[arg(help = "Input message")]
19        input: String,
20        #[arg(short, long, help = "HMAC key")]
21        key: String,
22    },
23    #[command(about = "HMAC-SHA256")]
24    Sha256 {
25        #[arg(help = "Input message")]
26        input: String,
27        #[arg(short, long, help = "HMAC key")]
28        key: String,
29    },
30    #[command(about = "HMAC-SHA512")]
31    Sha512 {
32        #[arg(help = "Input message")]
33        input: String,
34        #[arg(short, long, help = "HMAC key")]
35        key: String,
36    },
37    #[command(about = "Verify HMAC-MD5 tag in constant time")]
38    VerifyMd5 {
39        #[arg(help = "Input message")]
40        input: String,
41        #[arg(short, long, help = "HMAC key")]
42        key: String,
43        #[arg(short, long, help = "Expected hex-encoded tag")]
44        tag: String,
45    },
46    #[command(about = "Verify HMAC-SHA1 tag in constant time")]
47    VerifySha1 {
48        #[arg(help = "Input message")]
49        input: String,
50        #[arg(short, long, help = "HMAC key")]
51        key: String,
52        #[arg(short, long, help = "Expected hex-encoded tag")]
53        tag: String,
54    },
55    #[command(about = "Verify HMAC-SHA256 tag in constant time")]
56    VerifySha256 {
57        #[arg(help = "Input message")]
58        input: String,
59        #[arg(short, long, help = "HMAC key")]
60        key: String,
61        #[arg(short, long, help = "Expected hex-encoded tag")]
62        tag: String,
63    },
64    #[command(about = "Verify HMAC-SHA512 tag in constant time")]
65    VerifySha512 {
66        #[arg(help = "Input message")]
67        input: String,
68        #[arg(short, long, help = "HMAC key")]
69        key: String,
70        #[arg(short, long, help = "Expected hex-encoded tag")]
71        tag: String,
72    },
73}
74
75pub fn run(action: HmacAction) -> Result<()> {
76    match action {
77        HmacAction::Md5 { input, key } => {
78            println!("{}", hmac_md5(key.as_bytes(), input.as_bytes()));
79        }
80        HmacAction::Sha1 { input, key } => {
81            println!("{}", hmac_sha1(key.as_bytes(), input.as_bytes()));
82        }
83        HmacAction::Sha256 { input, key } => {
84            println!("{}", hmac_sha256(key.as_bytes(), input.as_bytes()));
85        }
86        HmacAction::Sha512 { input, key } => {
87            println!("{}", hmac_sha512(key.as_bytes(), input.as_bytes()));
88        }
89        HmacAction::VerifyMd5 { input, key, tag } => {
90            print_verify(verify_md5(key.as_bytes(), input.as_bytes(), &tag));
91        }
92        HmacAction::VerifySha1 { input, key, tag } => {
93            print_verify(verify_sha1(key.as_bytes(), input.as_bytes(), &tag));
94        }
95        HmacAction::VerifySha256 { input, key, tag } => {
96            print_verify(verify_sha256(key.as_bytes(), input.as_bytes(), &tag));
97        }
98        HmacAction::VerifySha512 { input, key, tag } => {
99            print_verify(verify_sha512(key.as_bytes(), input.as_bytes(), &tag));
100        }
101    }
102    Ok(())
103}
104
105fn print_verify(is_match: bool) {
106    // Distinct exit words keep shell scripts robust.
107    if is_match {
108        println!("ok");
109    } else {
110        println!("mismatch");
111    }
112}
113
114fn hmac<D: Digest>(key: &[u8], message: &[u8], block_size: usize) -> Vec<u8> {
115    let actual_key = if key.len() > block_size {
116        let mut hasher = D::new();
117        hasher.update(key);
118        hasher.finalize().to_vec()
119    } else {
120        key.to_vec()
121    };
122
123    let mut padded_key = vec![0u8; block_size];
124    padded_key[..actual_key.len()].copy_from_slice(&actual_key);
125
126    let ipad: Vec<u8> = padded_key.iter().map(|&k| k ^ 0x36).collect();
127    let opad: Vec<u8> = padded_key.iter().map(|&k| k ^ 0x5c).collect();
128
129    let mut inner_hasher = D::new();
130    inner_hasher.update(&ipad);
131    inner_hasher.update(message);
132    let inner_hash = inner_hasher.finalize();
133
134    let mut outer_hasher = D::new();
135    outer_hasher.update(&opad);
136    outer_hasher.update(&inner_hash);
137    outer_hasher.finalize().to_vec()
138}
139
140pub fn hmac_md5(key: &[u8], message: &[u8]) -> String {
141    hex::encode(hmac::<Md5>(key, message, 64))
142}
143
144pub fn hmac_sha1(key: &[u8], message: &[u8]) -> String {
145    hex::encode(hmac::<Sha1>(key, message, 64))
146}
147
148pub fn hmac_sha256(key: &[u8], message: &[u8]) -> String {
149    hex::encode(hmac::<Sha256>(key, message, 64))
150}
151
152pub fn hmac_sha512(key: &[u8], message: &[u8]) -> String {
153    hex::encode(hmac::<Sha512>(key, message, 128))
154}
155
156/// Constant-time byte-slice equality check.
157///
158/// Returns `true` iff `a` and `b` have identical length and contents. The
159/// running time is proportional to `a.len()` regardless of where (or whether)
160/// the first differing byte occurs, which is the property MAC verification
161/// needs to avoid leaking secret tags through timing side channels.
162///
163/// Using `==` on `&[u8]` or comparing two hex-encoded `String`s would short
164/// circuit at the first mismatch and is therefore unsafe in this role.
165#[inline]
166fn ct_eq(a: &[u8], b: &[u8]) -> bool {
167    if a.len() != b.len() {
168        return false;
169    }
170    let mut diff: u8 = 0;
171    for (x, y) in a.iter().zip(b.iter()) {
172        diff |= x ^ y;
173    }
174    diff == 0
175}
176
177fn verify_with<D: Digest>(
178    key: &[u8],
179    message: &[u8],
180    expected_tag_hex: &str,
181    block_size: usize,
182) -> bool {
183    match hex::decode(expected_tag_hex) {
184        Ok(expected) => ct_eq(&hmac::<D>(key, message, block_size), &expected),
185        Err(_) => false,
186    }
187}
188
189/// Verifies an HMAC-MD5 tag against a freshly computed MAC in constant time.
190pub fn verify_md5(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
191    verify_with::<Md5>(key, message, expected_tag_hex, 64)
192}
193
194/// Verifies an HMAC-SHA1 tag against a freshly computed MAC in constant time.
195pub fn verify_sha1(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
196    verify_with::<Sha1>(key, message, expected_tag_hex, 64)
197}
198
199/// Verifies an HMAC-SHA256 tag against a freshly computed MAC in constant time.
200pub fn verify_sha256(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
201    verify_with::<Sha256>(key, message, expected_tag_hex, 64)
202}
203
204/// Verifies an HMAC-SHA512 tag against a freshly computed MAC in constant time.
205pub fn verify_sha512(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
206    verify_with::<Sha512>(key, message, expected_tag_hex, 128)
207}