写点什么

Rust 下 SM4/AES/RSA 加解密

作者:dlzht
  • 2024-07-20
    江苏
  • 本文字数:2486 字

    阅读完需:约 8 分钟

Rust下SM4/AES/RSA加解密

aesrsa加密使用非常广泛, 而sm4在信创系统中比较常见, 以前用Java开发的时候, 有强大的bc库, 还有易用的hutool, 现在转向了Rust, 也是找到了RustCrypto这个项目.


RustCrypto有点类似于bc, 包含的东西非常多, 编码, 哈希, 签名, 加解密, 椭圆曲线等等. 常用的套件差不多准备全了, 这就来放手试试. 这个项目的文档还不那么完善, 而且代码库分得比较细, 我在使用中也是绕了一些弯路, 这篇文章呢就记录一下.


分块加密, 工作模式, 填充模式

** 分块加密**: 属于对称密钥算法, 加解密用的是同一个密钥. 分块的意思是, 在加解密过程中, 会把数据切分成等长的块来处理. RustCrypto 的实现在block-ciphers, sm4rsa都属于分块加密算法 .


工作模式: 把初始向量, 密钥, 原文和密文这些分好块后, 要按什么顺序计算, 最终才得到加密的密文(或解密的原文). 工作模式和分块加密是组合关系, 一种加密算法能选用多种工作模式, 一种工作模式也能适用多种加密算法. RustCrypto 的实现在block-modes.


填充模式: 定义了怎么补全不完整的块, 上面提到分组加密需要把数据切分块处理, 比如 16 字节是一块, 而我们提供的数据并不总会是 16 的倍数, 这样最后一个块就可能会有"空白"部分, 而填充模式就决定了空白是什么内容. RustCrypto 的填充模式实现在block-padding.

SM4 加密/解密

[dependencies]sm4 = "0.5"cbc = { version = "0.1", features = ["alloc"] }
use sm4::cipher::block_padding::Pkcs7;use sm4::cipher::{BlockEncryptMut, KeyIvInit};
复制代码


上面是 cbc+sm4 加解密过程中要用到的依赖, 有些依赖项是通过sm4重新导出来的, 比如填充模式Pkcs7. 需要使用什么加密算法, 最好用这个库里导出的, 不然在编译时可能会出现晦涩的错误(一大串的泛型~).


//  CBC块模式加密type Sm4CbcEnc = cbc::Encryptor<sm4::Sm4>;
fn sm4_encrypt_cbc_pkcs7(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> { let sm4 = Sm4CbcEnc::new(key.into(), iv.into()); return sm4.encrypt_padded_vec_mut::<Pkcs7>(data);}
复制代码


这里给cbc::Encryptor<sm4::Sm4>起了个别名Sm4CbcEnc, 方法第一步是构造加密器, 参数是密钥key和初始向量iv. 虽然这两个参数的类型是切片, 但对长度是有一定要求的, keyiv得是 16 字节, 不然会panic.


data参数是要加密的原数据, 如果采用的填充模式是NoPadding(不填充), 那数据的长度就得是 16 的倍数, 其他填充模式则不需要, 这里是用pkcs7模式来填充.


方法的第二步加密原文, 并返回密文字节. encrypt_padded_vec_mut方法会自动分配内存来放结果, 这需要开启alloc特性. 如果没有开启这个特性的话, 需要自己申请内存, 这样可以在没有std的环境中使用.


//  CBC块模式解密type Sm4CbcDec = cbc::Decryptor<sm4::Sm4>;
fn sm4_decrypt_cbc_pkcs7(key: &[u8], iv: &[u8], data: &[u8]) -> Vec<u8> { let sm4 = crate::Sm4CbcDec::new(key.into(), iv.into()); return sm4.encrypt_padded_vec_mut::<Pkcs7>(data);}
复制代码


解密代码和加密区别不大, 一个是改成了解密器cbc::Decryptor<sm4::Sm4>, 还一个是改成解密方法encrypt_padded_vec_mut. key, iv, 还有填充模式和数据长度, 和加密过程的要求一样.

AES 加密/解密

[dependencies]aes = "0.8"ofb = "0.6"
use aes::cipher::{KeyIvInit, StreamCipher};
复制代码


某些工作模式可以让分块密码像流密码一样工作, 也就是不需要非得是完整的块, 也就不需要填充字节. ctrofb就是这样的模式, 接下来的aes加密我就搭配ofb的工作模式, 上面是我们需要用到的依赖.


type Aes128OfbEnc = ofb::Ofb<aes::Aes128>;
fn aes_encrypt_ofb(key: &[u8], iv: &[u8], data: &mut [u8]) { let mut aes = Aes128OfbEnc::new(key.into(), iv.into()); aes.apply_keystream(data);}
复制代码


aes的密钥长度可以是 16, 24 或 32 字节, 密钥的长度不同, 需要用到的类型也不同. 上面代码是 16 字节长度密钥的实例, 选用的类型是Aes128, 128b = 16B. 其他长度的密钥, 可以用Aes192, Aes256


type Aes128OfbDec = ofb::Ofb<aes::Aes128>;
fn aes_decrypt_ofb(key: &[u8], iv: &[u8], data: &mut [u8]) { let mut aes = Aes128OfbEnc::new(key.into(), iv.into()); aes.apply_keystream(data);}
复制代码


加解密的代码是一样的, 别名类型是为了看上去有所区别. apply_keystream方法可以把密文数据存在原数据的位置, 所以这边的data传递的是可变引用, 这样在不再需要原文数据的情况下, 就省去了分配额外的内存的开销.

RSA 加密/解密

// [dependencies] // rsa = "0.9"pub fn generate_keypair(length: usize) -> Result<(RsaPublicKey, RsaPrivateKey)> {    let mut rng = rand::thread_rng();    let pri_key = RsaPrivateKey::new(&mut rng, length)?;    let pub_key = pri_key.to_public_key();    return Ok((pub_key, pri_key));}
复制代码


RustCrypto 的实现在仓库RSA, 这是一种非对称密钥算法, 有公钥pub_key和私钥pri_key两个密钥. 公钥用来加密, (公钥Φ原文)=密文; 私钥则用来解密, (私钥Φ密文)=原文. 上面是生成密钥的代码, 参数length指密钥长度为 2<sup>length</sup>.


pub fn encrypt_byte(pub_der: &[u8], data: &[u8]) -> Result<Vec<u8>> {    let mut rng = rand::thread_rng();    // 从字节中解析公钥    let pub_key = RsaPublicKey::from_public_key_der(pub_der)?;    // 公钥加密    return Ok(pub_key.encrypt(&mut rng, Pkcs1v15Encrypt, data)?);}
pub fn decrypt_byte(pri_der: &[u8], data: &[u8]) -> Result<Vec<u8>> { // 从字节中解析私钥 let pri_key = RsaPrivateKey::from_pkcs8_der(pri_der)?; // 私钥加密 return Ok(pri_key.decrypt(Pkcs1v15Encrypt, data)?);}
复制代码


公私钥可以用pem(标头 + Base64 数据), der(二进制数据)等方式编码, 私钥还有pkcs1pkcs8格式的区别, 库里都提供了对应的解析方法. 上面实例代码中是解析 的der格式. rsa的加解密用起来说更简单一些, 因为没有了工作模式, 不过填充模式还是有, 这边用的 是Pkcs1v15Encrypt.


原文发布在 Github: https://dlzht.github.io/012-rustxia-sm4-aes-rsajia-jie-mi/

用户头像

dlzht

关注

还未添加个人签名 2020-07-28 加入

还未添加个人简介

评论

发布
暂无评论
Rust下SM4/AES/RSA加解密_rust_dlzht_InfoQ写作社区