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 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#[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
189pub fn verify_md5(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
191 verify_with::<Md5>(key, message, expected_tag_hex, 64)
192}
193
194pub fn verify_sha1(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
196 verify_with::<Sha1>(key, message, expected_tag_hex, 64)
197}
198
199pub fn verify_sha256(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
201 verify_with::<Sha256>(key, message, expected_tag_hex, 64)
202}
203
204pub fn verify_sha512(key: &[u8], message: &[u8], expected_tag_hex: &str) -> bool {
206 verify_with::<Sha512>(key, message, expected_tag_hex, 128)
207}