分组密码模式
分组模式
分组密码只能处理固定长度的分组,但是对于如何把每个分组组合起来,这里面也会有多种方案,一个非常简单的做法便是每个分组单独处理,然后把每个分组的结果拼接起来,这种方式其实也就是「ECB」模式,这个模式实际上是非常不安全的,各位读者在生产环境千万不要使用,本文在加下将描述几种模式。
ECB模式
ECB模式(「Electronic CodeBook」),在之前写的文章当中,我反复使用了这个模式,这个模式也就是将明文分组加密之后直接转换成为密文分组,也就是说,如果分组相同,则加密之后的密文也相同。
ECB模式
如果最后一个明文无法满足一个分组长度,将用到上一篇文章所讲的Padding。之前我反复强调,这个模式实际上是不安全的,下面我来举个栗子来说明一下这个模式为什不安全。
首先来看一张图。
下面我们通过ECB模式和CBC模式进行加密。
ECB | CBC | CFB |
可以看到,使用ECB模式加密之后的图片,并没有完全隐藏图片的特征,还是可以隐约看到图片当中的文字的,而使用CBC等其他模式加密之后的图片,则完全看不出来原图的特征。
下面再来看另外一个例子,这个例子来源于深入浅出密码学这一本书,我对这个例子进行了进一步的简化,如下图所示:
ECB-Attack
假设攻击者Oscar可以拥有A和B银行的账号,以及可以截获流量进行替换,这样便可以通过替换将原来的接收账号替换成为Oscar的账号,自始至终,Oscar并没没有对密文进行解密。
由于「ECB」模式不能很好的隐藏明文的特征,所以这个方式是极不安全的,再次说一下,「不要用这个模式!!!」。
CBC模式
CBC模式(「Cipher Block Chaining」),分组连接模式,这个模式的叫法比较形象,如下图所示:
CBC模式
首先第一个分组,先和一个初始化向量进行异或运算,然后下一个分组和上一个分组的密文进行异或,就像一条链条一样,把每个分组串联起来,同ECB一样,这个模式也同样要求需要原始的输入需要满足分组的整数倍,否则就需要进行填充。
这种模式下,即使是两个分组内容相同,由于添加了和上一个分组关联的异或运算,得到的密文也不相同,就不会出现之前ECB模式的问题。
CFB模式
CFB模式(「Cipher Feedback」),密文反馈模式,在该模式下,前一个密文分组会送到下一个分组加密的输入端,也就是说,下一次分组加密要用到上一次加密的结果。
CFB模式
这么一说,有读者会发问了,这和CBC模式有什么区别,CBC模式不也用到了上一个分组的加密结果吗? 这里不同是CBC模式用的是上一次加密之后的密文和明文进行异或,然后执行加密函数,而CFB模式是对上一次加密的密文进行加密然后和明文分组进行异或得到下一个密文分组。另外这个模式由于不需要明文分组直接参与加密函数的运算,因此这个模式具有流密码的特征,也就是不要求明文满足分组的整数倍。
OFB模式
OFB模式(「Output-Feedback」)输出反馈模式,该模式下密码算法的输出会反馈到下一次的输入当中,如下图所示:
OFB模式
这个模式和CFB模式进行对比的话,这个模式没有使用密文分组,而是通过每一轮加密函数输出直接进入了下一轮加密的输入,同样,这个模式也不要求明文分组满足分块的整数倍。
CTR模式
CTR模式(「CounTeR」)计数器模式,该模式是一种通过逐次累加的计数器进行加密从而产生密钥流的模式,如下图所示:
CTR模式
这个模式是由计数器直接作为每个加密函数的输入,由于不依赖于上一步的输入,因此这个模式是可以并行计算的,可以提高加密速度。
模式对比
下面列一个表对几种模式进行一下对比,这个对比参考了实用密码学,图解密码技术,深入产出密码学等书籍,在这里感谢这些著作的作者。
模式 | 名称 | 是否需要填充 | 加密并行 | 解密并行 | 明文分组是否直接参与加密 | 是否推荐使用 |
ECB | 电子密码本模式 | ✅ | ✅ | ✅ | ✅ | ❌ |
CBC | 分组连接模式 | ✅ | ❌ | ✅ | ✅ | ✅ |
CFB | 密文反馈模式 | ❎ | ❌ | ✅ | ❌ | ✅ |
OFB | 输出反馈模式 | ❎ | ❌ | ❌ | ❌ | ✅ |
CTR | 计数器模式 | ❎ | ✅ | ✅ | ❌ | ✅ |
代码实现
由于目前我个人水平有限,我就先用SM4作为样例实现以下上面提到的模式吧,支持一下国密算法。
pub enum Mode { ECB, CBC, CFB, CTR, OFB, } pub struct CipherMode { cipher: SM4, mode: Mode, } 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 } fn block_add_one(a: &mut [u8]) { let mut carry = 1; for i in 0..16 { let (t, c) = a[15 - i].overflowing_add(carry); a[15 - i] = t; if !c { return; } carry = c as u8; } } impl CipherMode { pub fn new(key: &[u8], mode: Mode) -> CipherMode { let cipher = SM4::new(key.try_into().expect("")); CipherMode { cipher, mode, } } pub fn encrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { if iv.len() != 16 { panic!("iv must be 16-byte long"); } match self.mode { Mode::ECB => self.ecb_encrypt(bytes, iv), Mode::CBC => self.cbc_encrypt(bytes, iv), Mode::CFB => self.cfb_encrypt(bytes, iv), Mode::OFB => self.ofb_encrypt(bytes, iv), Mode::CTR => self.ctr_encrypt(bytes, iv), } } fn ecb_encrypt(&self, bytes: &[u8], _iv: &[u8]) -> Vec<u8> { bytes.chunks(16) .map(|block| self.cipher.encrypt_block(block.try_into().expect(""))) .flatten().collect::<Vec<_>>() } fn cbc_encrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { let mut out: Vec<u8> = Vec::new(); let mut vec_buf = [0; 16]; vec_buf.copy_from_slice(&iv); for chunk in bytes.chunks(16) { let enc = self.cipher.encrypt_block(&block_xor(&vec_buf, chunk)); out.extend_from_slice(&enc); vec_buf = enc; } out } fn cfb_encrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { let mut out: Vec<u8> = Vec::new(); let mut vec_buf: Vec<u8> = vec![0; 16]; vec_buf.clone_from_slice(iv); for chunk in bytes.chunks(16) { let enc = self.cipher.encrypt_block(&vec_buf[..].try_into().expect("")); if chunk.len() == 16 { let ct = block_xor(&enc, chunk); out.extend_from_slice(&ct); vec_buf.clone_from_slice(&ct); } else { for (i, item) in chunk.iter().enumerate() { out.push(*item ^ enc[i]) } } } return out; } fn ofb_encrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { let mut out: Vec<u8> = Vec::new(); let mut vec_buf: Vec<u8> = vec![0; 16]; vec_buf.clone_from_slice(iv); for chunk in bytes.chunks(16) { let enc = self.cipher.encrypt_block(&vec_buf[..].try_into().expect("")); if chunk.len() == 16 { out.extend_from_slice(&block_xor(&enc, chunk)); vec_buf.clone_from_slice(&enc); } else { for (i, item) in chunk.iter().enumerate() { out.push(*item ^ enc[i]) } } } return out; } fn ctr_encrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { let mut out: Vec<u8> = Vec::new(); let mut vec_buf: Vec<u8> = vec![0; 16]; vec_buf.clone_from_slice(iv); for chunk in bytes.chunks(16) { let enc = self.cipher.encrypt_block(&vec_buf[..].try_into().expect("")); if chunk.len() == 16 { out.extend_from_slice(&block_xor(&enc, chunk)); block_add_one(&mut vec_buf[..]); } else { for (i, item) in chunk.iter().enumerate() { out.push(*item ^ enc[i]) } } } return out; } fn decrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { if iv.len() != 16 { panic!("iv must be 16-byte long"); } match self.mode { Mode::ECB => self.ecb_decrypt(bytes, iv), Mode::CBC => self.cbc_decrypt(bytes, iv), Mode::CFB => self.cfb_decrypt(bytes, iv), Mode::OFB => self.ofb_encrypt(bytes, iv), Mode::CTR => self.ctr_encrypt(bytes, iv), } } fn ecb_decrypt(&self, bytes: &[u8], _iv: &[u8]) -> Vec<u8> { bytes.chunks(16) .map(|block| self.cipher.decrypt_block(block.try_into().expect(""))) .flatten().collect::<Vec<_>>() } fn cbc_decrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { let mut out: Vec<u8> = Vec::new(); let mut vec_buf = [0; 16]; vec_buf.copy_from_slice(&iv); for chunk in bytes.chunks(16) { let enc = self.cipher.decrypt_block(chunk); let ct = block_xor(&vec_buf, &enc); out.extend_from_slice(&ct); vec_buf.copy_from_slice(chunk); } return out; } fn cfb_decrypt(&self, bytes: &[u8], iv: &[u8]) -> Vec<u8> { let mut out: Vec<u8> = Vec::new(); let mut vec_buf: Vec<u8> = vec![0; 16]; vec_buf.clone_from_slice(iv); for chunk in bytes.chunks(16) { let enc = self.cipher.encrypt_block(&vec_buf[..].try_into().expect("")); if chunk.len() == 16 { out.extend_from_slice(&block_xor(&enc, chunk)); vec_buf.clone_from_slice(chunk); } else { for (i, &item) in chunk.iter().enumerate() { out.push(item ^ enc[i]); } } } return out; } } #[cfg(test)] mod test { use crate::cipher_mode::{CipherMode, Mode}; #[test] fn test() { let cipher = CipherMode::new("1234567890123456".as_bytes(), Mode::ECB); let result = cipher.encrypt("12345678901234561234567890123456".as_bytes(), "1234567890123456".as_bytes()); println!("{:?}", result); let result = cipher.decrypt(&result, "1234567890123456".as_bytes()); println!("{:?}", result); } #[test] fn test_cbc() { let cipher = CipherMode::new("1234567890123456".as_bytes(), Mode::CBC); let result = cipher.encrypt("12345678901234561234567890123456".as_bytes(), "1234567890123456".as_bytes()); println!("{:?}", result); let result = cipher.decrypt(&result, "1234567890123456".as_bytes()); println!("{:?}", result); } #[test] fn test_cfb() { let cipher = CipherMode::new("1234567890123456".as_bytes(), Mode::CFB); let result = cipher.encrypt("12345678901234561234567890123456123".as_bytes(), "1234567890123456".as_bytes()); println!("{:?}", result); let result = cipher.decrypt(&result, "1234567890123456".as_bytes()); println!("{:?}", result); } #[test] fn test_ofb() { let cipher = CipherMode::new("1234567890123456".as_bytes(), Mode::OFB); let result = cipher.encrypt("12345678901234561234567890123456123".as_bytes(), "1234567890123456".as_bytes()); println!("{:?}", result); let result = cipher.decrypt(&result, "1234567890123456".as_bytes()); println!("{:?}", result); } #[test] fn test_ctr() { let cipher = CipherMode::new("1234567890123456".as_bytes(), Mode::CTR); let result = cipher.encrypt("12345678901234561234567890123456123".as_bytes(), "1234567890123456".as_bytes()); println!("{:?}", result); let result = cipher.decrypt(&result, "1234567890123456".as_bytes()); println!("{:?}", result); } }
从理论上来说,把初始化的换成AES也行,这里我偷懒了,先这么写,等我想到优雅的方案,可能会重新实现一下。