shadowsocks / crypto2

The fastest cryptographic library in the galaxy
MIT License
350 stars 39 forks source link

sm4 - cbc 模式加密,要求【明文】长度是 16 的整数倍,扩容填充 0,造成解密后 \u{0} 出现。 #3

Closed stuartZhang closed 3 years ago

stuartZhang commented 3 years ago

大神,您的 crypto 库真心地佬强大了。

crypto2 = {git = "https://github.com/shadowsocks/crypto", branch = "master", package = "crypto"}

但是,在这里我的问题来了。解密之后的明文字符串是

\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}\u{0}abcde,123

我分析:上面左侧一连串的 \u{0} 是我在加密过程中,给 &[u8] 数组左填充的 0。右侧的解决结果 abcde,123 是正确的。太牛了!!!

大神,对于潜在任意长度的【明文】 字符串,我如何才能避免或解决(加密过程中)由 &[u8] 数组扩容(长度必须是 16 的整数倍)填充值 0 造成的 解密结果里的 \u{0} 呀?

大神,我不知道我在上面的描述是否清晰。但,还是请求您的帮助。下面附上我的代码:

use base64;
use crypto2::blockmode::Sm4Cbc;
use std::error::Error;
/// 加密
pub fn encrypt_cbc(text: &str, iv: &str, key: Option<String>) -> String {
    let key = match key {
        Some(value) => value,
        None => CIPHER_IV.to_string()
    };
    let mut cipher = Sm4Cbc::new(key.as_bytes(), iv.as_bytes());
    let cipher_text = text.as_bytes(); // 明文字符串 -> 明文 &[u8]
    let original_len = cipher_text.len(); 
    let pad_len = ((original_len as f64 / Sm4Cbc::BLOCK_LEN as f64).ceil() * 16_f64) as usize; // 计算 16 的整数倍
    let mut cipher_text1: Vec<u8> = Vec::new();
    for _ in original_len..pad_len {
        cipher_text1.push(0);
    }
    cipher_text1.extend_from_slice(cipher_text); // 扩容与左侧填充 0
    cipher.encrypt(&mut cipher_text1);
    base64::encode(cipher_text1)
}
/// 解密
pub fn decrypt_cbc(cipher_text: &str, iv: &str, key: Option<String>) -> Result<String, Box<dyn Error>> {
    let key = match key {
        Some(value) => value,
        None => CIPHER_IV.to_string()
    };
    let cipher_text = base64::decode(cipher_text)?;
    let mut cipher = Sm4Cbc::new(key.as_bytes(), iv.as_bytes());
    let mut cipher_text: Vec<u8> = cipher_text.iter().cloned().collect();
    cipher.decrypt(&mut cipher_text);
    match String::from_utf8(cipher_text) {
        Ok(rs) => Ok(rs),
        Err(err) => Err(Box::new(err))
    }
}

大神,我想在项目里使用您这个加密库。sm4 - cbc 模式就差最后这么一点就搞定了。希望获得您的指点与帮助。

stuartZhang commented 3 years ago

大神,我以【左填充对齐】的方式,当遇到【明文】长度不是 16 字节的整数倍时,左填充 0 至 utf-8 字节数组的左侧,再执行【sm4 - cbc 加密】;另一方面,在【解密】过程中,先执行【sm4 - cbc 解密】,再扣掉【明文】utf-8 字节数组左侧的 0 字节,再将 utf-8 字节数组 变换成 utf-8 字符串。

上面这个作法的确可以成功地【加密】与【解密】本文密码信息。但是,要求【前端】与【后端】都使用我这套实现。

我想问:关于【填充对齐】方式,是否有规范的作法或统一的标准?

这样的话,【前端】可以接着使用您的库和 rust + wasm 包,而【后端】可以使用 JAVA 实现的【sm4 - cbc 解密】包。别我在【前端】加密之后,因为对【填充对齐】的约定与假设不同,造成【后端】JAVA 版的【sm4 - cbc 解密】不能获得正确的值。

LuoZijun commented 3 years ago

CBC 以及 ECB 分组工作模式,本身依然是块密码,需要自己手动对齐数据至块大小的倍数为此。 同时 CFB64 在和块大小为 16 Bytes 的块密码协同工作时,也需要自己手动处理对齐问题。

你如果不想要自己手动处理对齐问题,可以选择其它不需要手动处理对齐数据的分组工作模式。 或者自己解决对齐问题。

我无法帮你选择任何方案。

QuantumGhost commented 3 years ago

@stuartZhang You may check PKCS7 padding.

stuartZhang commented 3 years ago

大神,我已经根据您提供的资料自己实现了 PKCS7 的块对齐。

pub fn encrypt_cbc(text: &str, iv: &str, key: Option<String>) -> String {
    let key = match key {
        Some(value) => value,
        None => CIPHER_IV.to_string()
    };
    let mut cipher = Sm4Cbc::new(key.as_bytes(), iv.as_bytes());
    let cipher_text = text.as_bytes();
    // 开始 - PKCS7 Padding 填充模式
    let original_len = cipher_text.len();
    // 下面:计算与 16 位大小的整数倍,差了几位。
    // 1. 若被加密明文长度正好是 16 位块的整数倍,就强制再追加 16 位补齐位。而且,每一位 u8 值就是 16
    // 2. 若被加密明文长度不是 16 位块的整数倍,就追加差的哪几位。而且,每一位 u8 值就是差的位数的个数。
    let pad_len = Sm4Cbc::BLOCK_LEN - original_len % Sm4Cbc::BLOCK_LEN; 
    let mut cipher_text1: Vec<u8> = Vec::with_capacity(original_len + pad_len);
    // 优先填上明文内容
    cipher_text1.extend_from_slice(cipher_text);
    // 再补上块对齐的补位
    for _ in 0..pad_len as u8 {
        cipher_text1.push(pad_len as u8);
    }
    // 结束 - PKCS7 Padding 填充模式
    cipher.encrypt(&mut cipher_text1);
    base64::encode(cipher_text1)
}