【密码学】分组密码模式

简介: 分组密码只能处理固定长度的分组,但是对于如何把每个分组组合起来,这里面也会有多种方案,一个非常简单的做法便是每个分组单独处理,然后把每个分组的结果拼接起来,这种方式其实也就是「ECB」模式,这个模式实际上是非常不安全的,各位读者在生产环境千万不要使用,本文在加下将描述几种模式。

分组密码模式


)IZH(RJY3QW{HR%5@6`8SMR.jpg分组模式

分组密码只能处理固定长度的分组,但是对于如何把每个分组组合起来,这里面也会有多种方案,一个非常简单的做法便是每个分组单独处理,然后把每个分组的结果拼接起来,这种方式其实也就是「ECB」模式,这个模式实际上是非常不安全的,各位读者在生产环境千万不要使用,本文在加下将描述几种模式。


ECB模式

ECB模式(「Electronic CodeBook」),在之前写的文章当中,我反复使用了这个模式,这个模式也就是将明文分组加密之后直接转换成为密文分组,也就是说,如果分组相同,则加密之后的密文也相同。

D285@B6YJ~MJPI178L]){{6.jpg

image.gifECB模式

如果最后一个明文无法满足一个分组长度,将用到上一篇文章所讲的Padding。之前我反复强调,这个模式实际上是不安全的,下面我来举个栗子来说明一下这个模式为什不安全。

首先来看一张图。image.gif

V2@6~(~DJD~_9_0D0SN2(AQ.png

下面我们通过ECB模式和CBC模式进行加密。

ECB CBC CFB

可以看到,使用ECB模式加密之后的图片,并没有完全隐藏图片的特征,还是可以隐约看到图片当中的文字的,而使用CBC等其他模式加密之后的图片,则完全看不出来原图的特征。

下面再来看另外一个例子,这个例子来源于深入浅出密码学这一本书,我对这个例子进行了进一步的简化,如下图所示:

1$MW_VQ0$MSP@W3ZJLRX4NV.jpgECB-Attack

假设攻击者Oscar可以拥有A和B银行的账号,以及可以截获流量进行替换,这样便可以通过替换将原来的接收账号替换成为Oscar的账号,自始至终,Oscar并没没有对密文进行解密。

由于「ECB」模式不能很好的隐藏明文的特征,所以这个方式是极不安全的,再次说一下,「不要用这个模式!!!」


CBC模式

CBC模式(「Cipher Block Chaining」),分组连接模式,这个模式的叫法比较形象,如下图所示:

@A~$0RE7Y4{HP}YYM874H9U.png

image.gifCBC模式

首先第一个分组,先和一个初始化向量进行异或运算,然后下一个分组和上一个分组的密文进行异或,就像一条链条一样,把每个分组串联起来,同ECB一样,这个模式也同样要求需要原始的输入需要满足分组的整数倍,否则就需要进行填充。

这种模式下,即使是两个分组内容相同,由于添加了和上一个分组关联的异或运算,得到的密文也不相同,就不会出现之前ECB模式的问题。


CFB模式

CFB模式(「Cipher Feedback」),密文反馈模式,在该模式下,前一个密文分组会送到下一个分组加密的输入端,也就是说,下一次分组加密要用到上一次加密的结果。

DJ~A$IK@`)A4[1$QM}1)P5B.png

image.gifCFB模式

这么一说,有读者会发问了,这和CBC模式有什么区别,CBC模式不也用到了上一个分组的加密结果吗? 这里不同是CBC模式用的是上一次加密之后的密文和明文进行异或,然后执行加密函数,而CFB模式是对上一次加密的密文进行加密然后和明文分组进行异或得到下一个密文分组。另外这个模式由于不需要明文分组直接参与加密函数的运算,因此这个模式具有流密码的特征,也就是不要求明文满足分组的整数倍。


OFB模式

OFB模式(「Output-Feedback」)输出反馈模式,该模式下密码算法的输出会反馈到下一次的输入当中,如下图所示:

8X%UOX1G)S)B~QDK~)RLTQA.pngOFB模式

这个模式和CFB模式进行对比的话,这个模式没有使用密文分组,而是通过每一轮加密函数输出直接进入了下一轮加密的输入,同样,这个模式也不要求明文分组满足分块的整数倍。


CTR模式

CTR模式(「CounTeR」)计数器模式,该模式是一种通过逐次累加的计数器进行加密从而产生密钥流的模式,如下图所示:

MYQ)U8LV4%79{NEDTCT)OXB.pngCTR模式

这个模式是由计数器直接作为每个加密函数的输入,由于不依赖于上一步的输入,因此这个模式是可以并行计算的,可以提高加密速度。


模式对比

下面列一个表对几种模式进行一下对比,这个对比参考了实用密码学,图解密码技术,深入产出密码学等书籍,在这里感谢这些著作的作者。

模式 名称 是否需要填充 加密并行 解密并行 明文分组是否直接参与加密 是否推荐使用
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也行,这里我偷懒了,先这么写,等我想到优雅的方案,可能会重新实现一下。

相关文章
|
5月前
|
算法 安全 数据安全/隐私保护
互联网并发与安全系列教程(13) - 信息加密技术(对称&非对称加密)
互联网并发与安全系列教程(13) - 信息加密技术(对称&非对称加密)
34 0
|
5月前
|
安全 算法 Java
互联网并发与安全系列教程(12) - 信息加密技术(单向散列加密)
互联网并发与安全系列教程(12) - 信息加密技术(单向散列加密)
43 0
|
存储 Rust 并行计算
【密码学】一文读懂XTS模式
这篇文章的灵感来源于我偶然翻到的一个某U盘有关磁盘加密的一个介绍(这一篇不是广告蛤), 然后发现这个模式我之前还真没遇到过,因此呢,就学习了一下,就出来了这一篇文章。
3482 0
【密码学】一文读懂XTS模式
|
Rust 算法 数据安全/隐私保护
【密码学】一文读懂XTEA加密
本篇文章,我们来看一下上一次讲过的TEA加密算法的一个升级版XTEA, 相比于TEA, XTEA的安全性显然是更高的,其中的过程要比TEA稍微复杂一点点。
1017 0
【密码学】一文读懂XTEA加密
|
存储 算法 数据安全/隐私保护
【密码学】一文读懂白盒AES(Chow方案)(一)
本文主要参考了文献^[1], 代码参考了^[2], 这里感谢文献作者和代码作者,如果有能力的大佬,可以自行查看原文献,个人水平有限,有哪里写的不对的地方,也欢迎读者指正。
2703 0
【密码学】一文读懂白盒AES(Chow方案)(一)
|
2月前
|
人工智能 分布式计算 安全
【现代密码学】笔记1.2 -- 对称密钥加密、现代密码学的基本原则《introduction to modern cryphtography》现代密码学原理与协议
【现代密码学】笔记1.2 -- 对称密钥加密、现代密码学的基本原则《introduction to modern cryphtography》现代密码学原理与协议
83 0
|
存储 安全 区块链
非对称加密与共识机制
非对称加密与共识机制
81 0
|
Rust 搜索推荐 算法
【密码学】一文读懂GCM(Golais计数器模式)
GCM(Galois计数器模式)同样的也是NIST提出来的,这个模式基于并行化设计,下面来一起看一下这个模式具体是如何工作的吧。
1227 0
【密码学】一文读懂GCM(Golais计数器模式)
|
Rust 算法 数据安全/隐私保护
【密码学】一文读懂白盒AES(Chow方案)(二)
本文主要参考了文献^[1], 代码参考了^[2], 这里感谢文献作者和代码作者,如果有能力的大佬,可以自行查看原文献,个人水平有限,有哪里写的不对的地方,也欢迎读者指正。
1242 0
【密码学】一文读懂白盒AES(Chow方案)(二)
|
算法 数据安全/隐私保护
【密码学】密码学概述
每个人都有自己的秘密,如果不加密,在网上传输很容易被监听。如果涉及到金钱相关,密码泄露以后很容易造成损失。所以都会利用加密 cryptography 技术,保证信息的机密性 confidentiality。信息被加密以后变成了密文在网上传播,接收者拿到密文进行解密 cryptanalysis,解密以后就可以看到明文。对称密码 (symmetric cryptography)是指在加密和解密时使用同一密钥的方式。对应的加密方式是对称加密。目前广泛使用 AES。对称密码有多种别名,公共密钥密码(common-k
142 0
【密码学】密码学概述