如何让Rust的openssl crate解密Ruby symmetric-encryption gem数据并去乱码
我之前在跨语言解密symmetric-encryption的数据时也踩过几乎一样的坑!Node的crypto库确实帮我们做了不少隐式处理,但Rust的openssl crate需要我们把每一步都掰碎了来,容不得半点马虎。结合你已经在Node实现的经验,咱们一步步排查乱码问题:
1. 先确认你是否正确处理了symmetric-encryption的完整加密结构
symmetric-encryption v2版本(最常用的)的加密字节流结构是:
[1字节版本号] + [IV] + [密文] + [HMAC校验值]
你说已经去除了头部,但可能没注意到末尾的HMAC部分——Node的crypto可能在验证后自动截断了这部分,但Rust不会。如果你的解密结果里带着HMAC的字节(通常是32字节的SHA256哈希),那转成字符串肯定会出现乱码。
解决方法:
- 先把Base64编码的加密字符串解码成字节数组
- 跳过第一个字节(版本号,通常是
0x02) - 提取IV:长度对应你用的加密算法,比如AES-CBC是16字节,AES-GCM是12字节
- 截取密文:从IV结束的位置开始,到倒数32字节(HMAC长度)结束
2. 别重复处理PKCS7填充
Node的crypto在CBC模式下默认会自动去除PKCS7填充,但Rust的openssl::symm::decrypt方法同样会自动处理!如果你已经手动去除了填充,相当于多删了几个字节,结果自然会乱码。
验证方法:注释掉你手动去填充的代码,直接用openssl的decrypt结果转字符串试试。
3. 确保编码转换正确
解密后的字节数组必须用UTF-8转成字符串(前提是原明文是UTF-8格式,这也是Rails的默认情况)。在Rust里,用String::from_utf8会严格校验编码,遇到无效字节直接报错;如果用String::from_utf8_lossy会把无效字节替换成�,这也能帮你判断是不是前面的步骤出了问题。
完整示例代码
假设你用的是symmetric-encryption默认的AES-256-CBC + HMAC-SHA256配置,Rust代码大概是这样:
use openssl::symm::{decrypt, Cipher}; use base64::decode; use std::error::Error; fn decrypt_rails_data(encrypted_b64: &str, encryption_key: &[u8]) -> Result<String, Box<dyn Error>> { // 1. 解码Base64加密字符串 let encrypted_bytes = decode(encrypted_b64)?; // 2. 校验版本号(v2版本) let version = encrypted_bytes[0]; if version != 0x02 { return Err("仅支持symmetric-encryption v2版本格式".into()); } // 3. 提取IV(AES-CBC默认16字节) let iv = &encrypted_bytes[1..17]; // 4. 提取密文(去掉版本、IV,最后去掉32字节的HMAC) let ciphertext = &encrypted_bytes[17..encrypted_bytes.len() - 32]; // 5. 解密(自动处理PKCS7填充) let decrypted_bytes = decrypt( Cipher::aes_256_cbc(), encryption_key, Some(iv), ciphertext, )?; // 6. 转成UTF-8字符串 Ok(String::from_utf8(decrypted_bytes)?) } // 测试调用 fn main() -> Result<(), Box<dyn Error>> { let encrypted = "你的Base64加密字符串"; let key = b"你的32字节AES密钥"; // AES-256需要32字节密钥 let plaintext = decrypt_rails_data(encrypted, key)?; println!("解密结果:{}", plaintext); Ok(()) }
最后核对配置
一定要保证Rust代码的配置和Rails的symmetric-encryption.yml完全一致:
- 加密算法(比如
aes-256-cbc还是aes-128-gcm) - IV长度(对应算法的要求)
- 是否启用了HMAC(如果没启用,就不用截断末尾的32字节)
- 密钥的长度(AES-256要32字节,AES-128要16字节)
内容的提问来源于stack exchange,提问作者Ethan Kent

