一文读懂CMAC
CMAC
介于上一篇文章比较水,然后这个和上一篇也比较相似,CMAC是为了解决DAA当中安全性不足的问题而出现的,这个算法一共有三个密钥,K, K1, K2, 其中K1和K2可以由K导出,接下来就来一起看一下CMAC的具体过程吧,这一篇文章其实也不长。
算法结构
对于CMAC来说,有两种情况,第一种是数据长度恰好就是分组长度的整数倍,对于这种情况,使用K1和最后一个分组异或之后加密得出结果,另一种情况是数据长度不是分组的整数倍,这就要先padding到分组的整数倍,Padding方法是先添加1bit的1, 其余bit填充0, 直到数据满足分组的整数倍,具体算法过程如下图所示:
结构
具体过程如下:
上面是密码学与网络安全书里面的公式描述, 这个描述我感觉有一点点小问题, 就是只有一个分组的时候,实际是没有上一个明文的, 也就是是不存在的,在实现的时候需要特殊注意一下这一点, 别问我咋发现的,看着书的公式,我大意了, 没有闪,实现完成之后和RFC的结果对比的时候发现不对, 然后重新看了一遍RFC, 主要原因还是因为我太菜^.^
。
密钥导出算法
前文说到过, 这里的K1和K2都可以由K导出, 其中K是AES的密钥,按照如下的方式计算K1和K2
这个乘法实际上是GF()上面的乘法,具体乘法细节就不描述了,参考代码或者维基百科对于这个乘法的描述吧,有关有限域相关的知识,可以参考之前写过的有限域相关的文章。
代码实现
依然用老演员rust来实现这个算法,这里我直接用AES来实现了,偷个小懒。
use std::num::ParseIntError; use aes::AES; use std::{fmt::Write}; pub struct CMAC {} fn block_xor(a: &[u8], b: &[u8]) -> [u8; 16] { let mut out = [0u8; 16]; for i in 0..16 { out[i] = a[i] ^ b[i]; } out } pub fn encode_hex(bytes: &[u8]) -> String { let mut s = String::with_capacity(bytes.len() * 2); for &b in bytes { write!(&mut s, "{:02x}", b).unwrap(); } s } fn u8to128(message: &[u8]) -> u128 { ((message[0] as u128) << 120) | ((message[1] as u128) << 112) | ((message[2] as u128) << 104) | ((message[3] as u128) << 96) | ((message[4] as u128) << 88) | ((message[5] as u128) << 80) | ((message[6] as u128) << 72) | ((message[7] as u128) << 64) | ((message[8] as u128) << 56) | ((message[9] as u128) << 48) | ((message[10] as u128) << 40) | ((message[11] as u128) << 32) | ((message[12] as u128) << 24) | ((message[13] as u128) << 16) | ((message[14] as u128) << 8) | ((message[15] as u128) << 0) } fn gf128_add_one(num: &[u8]) -> Vec<u8> { let mut val = u8to128(num); if val & 0x80000000000000000000000000000000 != 0 { val <<= 1; val ^= 0x87; } else { val <<= 1; } val.to_be_bytes().to_vec() } impl CMAC { fn digest(key: &[u8], message: &[u8]) -> String { let mut aes = AES::new(key); let k0 = aes.encrypt_block(&[0; 16]); let k1 = gf128_add_one(k0.as_ref()); let k2 = gf128_add_one(&k1); println!("k0 = {:x?}", k0); println!("k1 = {:x?}", k1); println!("k2 = {:x?}", k2); let mut padding_message = message.clone().to_vec(); let mut remain_len = padding_message.len() % 16; if padding_message.len() == 0 { remain_len = 16; } if remain_len != 0 { padding_message.push(0x80); remain_len -= 1; while remain_len > 0 { padding_message.push(0x0); remain_len -= 1; } } println!("padding_message: {:?}", padding_message); let mut remain_len = padding_message.len() % 16; if message.len() == 0 { remain_len = 16; } let mut result = vec![0u8; padding_message.len()]; let mut block = [0u8; 16]; let mut buffer = [0u8; 16]; if result.len() == 16 { for j in 0..16 { block[j] = padding_message[j]; } if remain_len > 0 { buffer = aes.encrypt_block(&block_xor(&block, &k2)); } else { buffer = aes.encrypt_block(&block_xor(&block, &k1)); } return encode_hex(&buffer); } for i in 0..padding_message.len() / 16 { for j in 0..16 { block[j] = padding_message[i * 16 + j]; } if i == 0 && i != padding_message.len() / 16 - 1 { buffer = aes.encrypt_block(&block); } else if i == padding_message.len() / 16 - 1 { if remain_len == 0 { buffer = aes.encrypt_block(&block_xor(&block_xor(&buffer, &block), &k1)); } else { buffer = aes.encrypt_block(&block_xor(&block_xor(&buffer, &block), &k2)); } } else { buffer = aes.encrypt_block(&block_xor(&buffer, &block)); } for j in 0..16 { result[i * 16 + j] = buffer[j]; } } return encode_hex(&buffer); } } 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() } #[cfg(test)] mod tests { use crate::{CMAC, decode_hex}; #[test] fn it_works() { let message = "".as_bytes(); let key = [43u8, 126, 21, 22, 40, 174, 210, 166, 171, 247, 21, 136, 9, 207, 79, 60]; let result = CMAC::digest( &key, &message, ); println!("{}", result); let message = "6bc1bee22e409f96e93d7e117393172a"; let message = decode_hex(&String::from(message)).unwrap(); let result = CMAC::digest( &key, &message, ); println!("{}", result); } }