美文网首页
AES CBC模式加密解密实操(Rust、Python、Go、J

AES CBC模式加密解密实操(Rust、Python、Go、J

作者: thepoy | 来源:发表于2022-10-27 19:17 被阅读0次

本文采用的是 CBC 模式,此模式的最大的特点之一即为流模式,最重要的就是每个加解密过程都使用不重复的、唯一的IV(初始化向量)。

网上充斥着许多固定 IV 的错误文章,不可采信。

0 密文的存储

在文章最开头,我们讨论一下密文如何保存的问题。

我说过了,IV必须随机生成,这也就意味着同一段明文生成的密文也将是随机的,每次都不相同。

很多人将 KEY 和 IV 保存成一个或 IV 固定,我就很好奇,那你为什么用 CBC 模式?直接删除 IV,换成 ECB 模式不好吗?

从下面代码的打印结果中你可以看到,密文和IV的长度分别是32、16,既然长度固定,那为什么不能将二者合并呢?

于我是保存的思路便是:

IV extend KEY

生成一个新的数组或bytes,前16位是IV,后32位是密文,合并后保存到数据库的一个字段里,查询时按长度分割即可。

1 Rust

开发的程序使用的就是 Rust,故而最初的加解密也使用 Rust 完成,使用的库:

依赖:

rand_core = { version = "0.6", features = ["std"] }
cbc = "0.1.2"
aes = "0.8"

代码:

use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use rand_core::{OsRng, RngCore};

type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;

const KEY: &[u8; 16] = b"abcdedghijklmnop"; // 模拟密钥,请勿在实际程序中使用

/// 生成随机 iv
fn generate_iv() -> [u8; 16] {
    let mut rng = OsRng;
    let mut bytes = [0u8; 16];
    rng.fill_bytes(&mut bytes);

    bytes
}

/// 加密
pub fn encrypt(plain: &[u8]) -> (Vec<u8>, [u8; 16]) {
    // 随机值
    let iv = generate_iv();

    let mut buf = [0u8; 48];
    let pt_len = plain.len();
    buf[..pt_len].copy_from_slice(plain);
    let ct = Aes128CbcEnc::new(KEY.into(), &iv.into())
        .encrypt_padded_b2b_mut::<Pkcs7>(plain, &mut buf)
        .unwrap();

    (ct.to_vec(), iv)
}

/// 解密
pub fn decrypt(cipher: &[u8], iv: [u8; 16]) -> Vec<u8> {
    let cipher_len = cipher.len();
    let mut buf = [0u8; 48];
    buf[..cipher_len].copy_from_slice(cipher);

    let pt = Aes128CbcDec::new(KEY.into(), &iv.into())
        .decrypt_padded_b2b_mut::<Pkcs7>(cipher, &mut buf)
        .unwrap();

    pt.to_vec()
}

fn main() {
    // 账号密码应为单向加密,参考:https://github.com/RustCrypto/password-hashes
    // 这里的示例代码应用来加密如手机号、身份证号、银行卡号等涉及用户隐私的数据
    let separator = "*".repeat(40);

    let plain = b"This is not a password";
    println!("明文:{:?}", plain);
    let (ct, iv) = encrypt(plain);
    println!(
        "{}\n密文:{:?}\n初始化向量:{:?}\n{}",
        separator, ct, iv, separator
    );
    let pt = decrypt(&ct, iv);
    println!("解密结果:{:?}", pt);

    assert_eq!(plain.to_vec(), pt);
}

打印结果:

明文:[84, 104, 105, 115, 32, 105, 115, 32, 110, 111, 116, 32, 97, 32, 112, 97, 115, 115, 119, 111, 114, 100]
****************************************
密文:[132, 69, 28, 205, 44, 250, 144, 23, 230, 230, 194, 204, 35, 167, 56, 224, 183, 193, 137, 108, 245, 203, 93, 164, 17, 188, 134, 59, 53, 192, 153, 130]
初始化向量:[240, 219, 228, 209, 163, 125, 234, 213, 88, 177, 17, 98, 255, 222, 149, 78]
****************************************
解密结果:[84, 104, 105, 115, 32, 105, 115, 32, 110, 111, 116, 32, 97, 32, 112, 97, 115, 115, 119, 111, 114, 100]

tips: 为什么使用 bytes?

密文终究是要保存到数据库中的,而且密文的形式或格式在数据库查询方面毫无区别, 毕竟都加密了,没有规律可言。

我选择的数据库是 PostgreSQL,bytea 的空间占用也比 varchar 和 char 小,查询速度也比二者快,所以无需转换成 base64 或其他形式的字符串。

2 Python

使用 Python 再完成一遍完全是因为我用 Python 写了一个迁移脚本,原始数据是明文,迁移到数据库里需要加密处理一下。

使用的库为:

代码:

import os

from typing import List, Optional, Union
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

KEY = b"abcdedghijklmnop"  # 模拟密钥,请勿在实际程序中使用
"""不涉及到文件io的时候,bytes 的默认编码就是 utf-8"""

BytesData = Union[List[int], str, bytes]
"""自定义数据类型,iv、plain、cipher 都可以使用此类型"""


class Cipher:
    @staticmethod
    def to_bytes(src: BytesData) -> bytes:
        """将一些其他类型的数据转换成 bytes"""
        if isinstance(src, list):
            return bytes(src)

        if isinstance(src, str):
            return src.encode()

        return src

    @staticmethod
    def generate_iv() -> bytes:
        """生成一个随机 iv(伪随机)"""
        return os.urandom(16)

    def encrypt(self, plain: str):
        """加密

        Parameters
        ----------
        plain : str
            待加密的明文

        Returns
        -------
        tuple(bytes, bytes)
            返回密文 bytes 和生成的 iv。
        """
        iv = self.generate_iv()

        cipher = AES.new(KEY, AES.MODE_CBC, iv)

        cipher_data = cipher.encrypt(pad(plain.encode(), AES.block_size))

        return cipher_data, iv

    def decrypt(self, cipher_data: BytesData, iv_data: BytesData):
        """解密

        Parameters
        ----------
        cipher_data : BytesData
            多个类型的密文。
        iv_data : BytesData
            iv 数据,如果你没有改代码,类型应该就是 bytes。

            当然你也可以传入 str、list[int]类型的数据。

        Returns
        -------
        bytes
            明文 bytes。
        """
        cipher = AES.new(KEY, AES.MODE_CBC, self.to_bytes(iv_data))

        plain_data = cipher.decrypt(self.to_bytes(cipher_data))

        return unpad(plain_data, AES.block_size)

    @staticmethod
    def to_byte_array(src: bytes):
        """将 bytes 转换成 list[int],与 rust 对比用的函数,你可以删掉"""
        return [i for i in src]


if __name__ == "__main__":
    separator = "*" * 40

    c = Cipher()

    plain = "This is not a password"
    print("明文:", c.to_byte_array(plain.encode()))

    ct, iv = c.encrypt(plain)

    print(separator)
    print("密文:%s", c.to_byte_array(ct))
    print("初始化向量:%s", c.to_byte_array(iv))
    print(separator)

    pt = c.decrypt(ct, iv)
    print("解密结果:", c.to_byte_array(pt))

打印结果:

明文: [84, 104, 105, 115, 32, 105, 115, 32, 110, 111, 116, 32, 97, 32, 112, 97, 115, 115, 119, 111, 114, 100]
****************************************
密文:[42, 240, 23, 146, 27, 103, 222, 252, 118, 70, 103, 147, 132, 168, 174, 145, 130, 65, 121, 220, 40, 173, 232, 34, 120, 212, 188, 179, 250, 38, 61, 37]
初始化向量:[20, 37, 230, 37, 209, 158, 136, 197, 18, 4, 45, 22, 121, 227, 165, 210]
****************************************
解密结果: [84, 104, 105, 115, 32, 105, 115, 32, 110, 111, 116, 32, 97, 32, 112, 97, 115, 115, 119, 111, 114, 100]

3 Go

我暂用不到,以后再补充。

4 JS/TS

除非你是真的想泄密,不然前端应该不会用这种对称加密。

我暂用不到,以后再补充。

相关文章

  • hls视频的加解密示例

    加密 hls的加密采用aes-128-cbc, 关于aes-128-cbc, 请参考前文使用openssl命令加解...

  • aes cbc加密

    aes 128 cbc加密 aes 256 cbc加密

  • AES加密解密

    AES加密 相比于其他加密,AES加密似乎模式很多,包括ECB、CBC等等等等,每个模式又包括IV参数和Paddi...

  • 日常知识记事本

    1、关于AES(以AES128为例)加密的CBC模式: 秘钥长度: 128位(16字节) 偏移量: 在CBC分组模...

  • 加解密AES128 CBC NoPadding

    加密 AES128 CBC NoPadding 将加密好的数据转成16进制的字符 解密AES128 CBC NoP...

  • 0610 - AES256CBC, my first wheel

    AES256CBC Wrapper for AES 256 CBC using Python. Get from ...

  • javascript 和 golang 前后端使用 AES 加密

    golang 前端javascript的代码 参考文章 golang、JS AES(CBC模式)加密解密兼容[ht...

  • 2018-06-01AES加密

    我们在传递敏感数据是,需要对data进行加密。我们采用高AES 128位加密。加密模式采用CBC,填充模式采用PK...

  • 加密方式

    加密方式 1.可加密,可解密 Base64编码 对称加密AES加密DES加密有两种模式,ECB和CBC 非对称加密...

  • AES加解密

    AES是最近比较流行的高级加密算法,是一种对称加密算法 在AES中有很多模式,ECB、CBC等等,这些可以自己找一...

网友评论

      本文标题:AES CBC模式加密解密实操(Rust、Python、Go、J

      本文链接:https://www.haomeiwen.com/subject/iduftdtx.html