一文读懂HMAC
HMAC
本文将来聊一聊基于哈希函数的消息认证码,在此之前,先来科普一下什么是 「消息认证码」 (MAC), 先来看一个简单的栗子
消息认证码
消息认证码
中国第一家票号--日升昌,诞生于清道光三年的陕西省平遥县,它采用了汉字当做密码,即用汉字代表数字当中的0-9,以及采用汉字表示一年12个月和30天。比如: 全年12个月可以用"谨防假票冒取, 勿忘细视书章", 三十天可以表示为: "堪笑世情薄,天道最公平,昧心图自利,阴谋害他人,善恶终有报,到头必分明", 代表银两的10个数字代码为: "赵氏连城璧,由来天下传", 如上图所示, 5月15日汇银三百两可以表示为"冒利连通流", 这里我稍稍做一点点的修改,将明文和密文都放在了一块,这样如果某个人篡改了某个日期,那么对应的汉字就会不一致,这就可以判断原始消息有没有发生篡改。(虽然这个安全性实际上是没有比之前高的,但是因为剧情需要,各位读者凑合着看一下吧 ^.^)。
消息认证码,是一种认证技术,他利用密钥来生成一个固定长度的短数据块,并将该数据块附加在消息之后,正如上面的那个简单的栗子,密钥可以看做是数字对应的汉字,当然上面的那个长度实际上是不固定的, 有点差别, 仅做参考。
在这种方法中, 有请密码学的老演员Alice和Bob, 他们共享密钥K, 如果Alice向Bob发送消息,则由A计算MAC, 他是消息和密钥的函数, 即:
消息和MAC一同发给Bob, 接收方收到消息用相同的密钥和算法进行计算,得到新的MAC, 并将这两个进行对比, 如果这两个一样,表明消息没有被篡改, 否则说明消息已经被更改。
Alice-Bob通信
基于Hash函数的MAC: HMAC
上面简单介绍了一下什么是MAC, 回到本文的主角,基于哈希函数设计的消息验证码(HMAC)。对于HMAC, 这实际上可以看做是一种结构,可以直接使用任意的现有的哈希函数,如果之前的哈希函数发现安全度不够了,则可以非常轻松的替换成为另一个安全性更高的哈希函数。
算法描述
算法描述
- 如果KEY大于分组长度,则对原始的KEY取哈希值得到
- 如果KEY小于分组长度,则在KEY后面填充0得到
- 如果KEY就等于分组长度了,则KEY就是
- 计算然后在后面拼接上原始消息后进行哈希得到新的值
- 计算然后拼接上一步得到的新的值,最后进行哈希得到最终的结果
整个流程看起来应该还是比较清晰的,如果不能理解文字的描述,可以借助图来帮助理解一下。
算法实现
这里,还是用老演员rust来实现这个算法,有关哈希函数的实现,可以去参考我之前写的文章,这里就不去重复贴一遍哈希函数实现的代码了。
简单解释一下代码,因为HMAC是一种结构,所以这次实现就不能默认固定死哈希算法了,因此先来定义一个trait, 这个可以理解为一个接口。
pub trait Digest { fn digest(&self, message: impl AsRef<[u8]>) -> String; }
这个trait接受任意消息然后输出对应的摘要, 然后让之前写过的hash函数实现这个trait。
impl Digest for MD5 { fn digest(&self, message: impl AsRef<[u8]>) -> String { let input = message.as_ref(); MD5::hash(input) } }
先来用MD5实现一下吧,这里实际上可以用任意的哈希函数。
pub fn decode_hex(s: &String) -> Result<Vec<u8>, ParseIntError> { (0..s.len()) .step_by(2) .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) .collect() } pub struct HMAC<D> where D: Digest + Default, { digest: D, } impl<D> HMAC<D> where D: Digest + Default, { fn new() -> Self { HMAC { digest: Default::default(), } } fn finalize(self, message: &mut Vec<u8>, key: &mut Vec<u8>) -> String { let mut padding_key = key.clone(); if key.len() > BLOCK_SIZE { padding_key = Vec::from(self.digest.digest(key).as_bytes()); } while padding_key.len() < BLOCK_SIZE { padding_key.push(0x0); } let mut o_key_pad = padding_key.iter().map(|&it| it ^ OPAD).collect::<Vec<_>>(); let mut i_key_pad = padding_key.iter().map(|&it| it ^ IPAD).collect::<Vec<_>>(); i_key_pad.append(message); let mut hash = decode_hex(&self.digest.digest(i_key_pad)).unwrap(); o_key_pad.append(&mut hash); self.digest.digest(o_key_pad) } } #[cfg(test)] mod tests { use crate::HMAC; use sha1::SHA1; use md5::MD5; #[test] fn it_works() { type HmacMD5 = HMAC<MD5>; let hmac = HmacMD5::new(); let result = hmac.finalize(&mut "123456".as_bytes().to_vec(), &mut "123456".as_bytes().to_vec()); println!("{}", result); } }
小结
本文简单介绍了一下什么是MAC, 以及HMAC的主要流程,文章最上面的例子可能些许的不恰当,还请各位读者海涵。