背景
- 我正在将Java8写的程序改造成Rust,其中有个AES加解密。
- Java里已经证实是调用
Cipher cipher = Cipher.getInstance("AES");
生成的bytes,结果等效于Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
- 即:采用了ECB模式,PKCS5Padding填充。
- 期间GPT对我一顿忽悠,最后我让它下岗了
Java采用的是AES128
寻找加密结果一致的算法库
我在Rust里尝试寻找加密结果与Java加密结果一致的库:
- ring(使用起来好复杂,可查看前贴):自己加解密可以,无法与Java一致,应该是模式和填充的问题,但是不熟悉Rust库,也没找到合适的例子,真复杂。
- aes+ecb:Pkcs7填充,结果与Java不一致
- pkcs5:自己加解密可以,结果与Java不一致
- magic-crypt:自己加解密可以,结果与Java不一致
- aes+cbc:自己加解密可以,结果与Java不一致
证实源头默认加密算法
最后从Java入手,寻找一致的加解密算法,经过对比java的各种明确模式和填充模式的结果,寻找到了Java默认的算法: 确定了加密模式是ECB模式,PKCS5Padding填充。继续回归aes+ecb。
SecureRandom random=SecureRandom.getInstance("SHA1PRNG");
random.setSeed(key.getBytes());
KeyGenerator kgen = KeyGenerator.getInstance("AES");
kgen.init(128, random);
SecretKey secretKey = kgen.generateKey();
...
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES/OFB/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES/CFB/PKCS5Padding");
Cipher cipher = Cipher.getInstance("AES");
...
最后结论:AES128 + ECB + PKCS5Padding。
限定Rust库范围寻找这几种模式进行解密验证
用Rust的aes+ecb库进行解密验证:查看了ecb的源码,有这几种填充模式:
- NoPadding
- ZeroPadding
- Pkcs7
- Iso10126
- AnsiX923
- Iso7816
尝试各种模式
各种填充模式尝试,发现都解密不出来。
- 提示:由于Java加密后产生的bytes[]转换为了十六进制字符串。所以我多列出来了Rust的对应方法。Java byte是有符号数字,Rust是无符号数字u8,不知道是否有影响。
- 我的aes+ecb代码:
Cargo.toml
ecb = "0.1.2"
aes = "0.8.4"
use base64::prelude::BASE64_STANDARD_NO_PAD;
use base64::Engine as _;
use aes::cipher::{block_padding::{AnsiX923, Iso10126, Iso7816, NoPadding, Pkcs7, ZeroPadding}, BlockDecryptMut, BlockEncryptMut, KeyInit};
type Aes128EcbEnc = ecb::Encryptor<aes::Aes128>;
type Aes128EcbDec = ecb::Decryptor<aes::Aes128>;
/// 十六进制转成u8 array,Java解密也是先讲hex转换为bytes
fn hex_to_u8_array(hex_str: &str, len: usize) -> Result<Vec<u8>, String> {
if hex_str.len() != len * 2 {
return Err("Invalid length".to_string());
}
// println!("hex_str: {}", hex_str);
let mut result = Vec::new();
for (i, chunk) in hex_str.chars().collect::<Vec<char>>().chunks(2).enumerate() {
// println!("i={i} chunk: {:?}", chunk);
let byte = u8::from_str_radix(&chunk.iter().collect::<String>(), 16)
.map_err(|_| "Invalid hexadecimal value".to_string())?;
result.push(byte);
}
Ok(result)
}
/// u8 array转成十六进制,Java加密后将bytes array转换为了hex
fn u8_array_to_hex(arr: &[u8]) -> String {
arr.iter()
.map(|&byte| format!("{:02X}", byte))
.collect::<String>()
}
/// key 的len都是16, 执行后,data已变为加密内容
fn aes_ecb_encrypt(key: &str, data: String) -> String {
let mut secret = [0u8; 16];
secret[..key.len()].copy_from_slice(&key.as_bytes());
let data = data.as_bytes();
// 目前data长度不会超过32
let mut buf = [0u8; 32];
let pt_len = data.len();
buf[..pt_len].copy_from_slice(&data);
let encrypt = Aes128EcbEnc::new(&secret.into())
.encrypt_padded_mut::<Pkcs7>(&mut buf, pt_len)
// .encrypt_padded_mut::<NoPadding>(&mut buf, pt_len)
.map_err(|e| println!("encrypt err : {}", e))
.unwrap();
println!("{:?}", encrypt);
let hex_str = u8_array_to_hex(encrypt);
println!("hex_str: {hex_str}");
hex_str
}
/// key 的len都是16,执行后,data已变为解密内容
fn aes_ecb_decrypt(key: &str, data: String) -> String {
if data.len() < 1 {
return "".to_string();
}
let mut secret = [0u8; 16];
secret[..key.len()].copy_from_slice(key.as_bytes());
let mut en_data = match hex_to_u8_array(&data, data.len() / 2) {
Ok(bytes) => bytes,
Err(e) => {
println!("data is err : {}, because : {}", data, e);
return "".to_string();
}
};
println!("{:?}", en_data);
// let size = (en_data.len() + 15) / 16;
let decrypt_padded_mut = Aes128EcbDec::new(&secret.into())
.decrypt_padded_mut::<Pkcs7>(&mut en_data);
// .decrypt_padded_mut::<NoPadding>(&mut en_data);
let decrypt = decrypt_padded_mut
.map_err(|e| println!("decrypt err : {}", e))
.unwrap();
String::from_utf8_lossy(decrypt).to_string()
}
mod test {
use super::{hex_to_u8_array, u8_array_to_hex};
#[test]
fn test_decrypt() {
// 临时起来个加密key
let key = "6661428";
// java用上key加密后的bytes array转换为十六进制字符串结果
let code = "02C282402EF9671561D56F9693AD6FBDAFA0AC62497EC19E1C6470E177142D48".to_string();
let text: String = crate::utils::crypto::aes_ecb_decrypt(key, code);
println!("text: {}", text);
// 明文
// println!("expect: 1726104884_1428_c816bd50266a");
assert_eq!(text, "1726104884_1428_c816bd50266a");
}
#[test]
fn test_aes() {
let key = "6661428";
let data = "1726104884_1428_c816bd50266a".to_string();
println!("raw: {:?}", data);
let code = crate::utils::crypto::aes_ecb_encrypt(key, data);
println!("output: {}", code);
println!("expect: 02C282402EF9671561D56F9693AD6FBDAFA0AC62497EC19E1C6470E177142D48");
let text = crate::utils::crypto::aes_ecb_decrypt(key, code);
println!("text: {}", text);
println!("expect: 1726104884_1428_c816bd50266a");
}
}
寻求高能大神帮忙看看Rust采用AES128+ECB+PKCS5Padding如何实现。
1
共 9 条评论, 1 页
评论区
写评论结贴,已经搞定,具体如下
// Java 代码,key是32位 hex string
//对应js处理
基于此,rust对应的写法(已验证可行):
Cargo.toml
sha1 = "0.10.6"
最终对应java的加解密 rust实现全码:
Cargo.toml
完整代码如下:
继续尝试了rand_aes库,仍然没与java一致。看来得找java的实现方法重新做一个库了。
Cargo.toml
输出:
输出:
--
👇
ppluck:
--
👇
TinusgragLin: > 即:找到java.security.SecureRandom在Rust中基于种子的同样算法实现即可
一定要同样的算法吗?只是(密码学安全地)随机产生 key 的话,感觉用 rand 库就好了,它的 thread_rng() 用的 StdRng 就是密码学安全的。
我在 github 上搜了一下 sha1 prng/sha1 rng/sha1 random,Rust 语言这边都没找到什么结果,要自己移植 Java 那边 SHA1PRNG 的实现的话应该要花不少时间的。
-- 是的,否则无法兼容生产上的流量,会解密失败。
用rand和rand_chacha
Cargo.toml
结果:
都与Java的不一致:
结果:
--
👇
TinusgragLin: > 即:找到java.security.SecureRandom在Rust中基于种子的同样算法实现即可
一定要同样的算法吗?只是(密码学安全地)随机产生 key 的话,感觉用 rand 库就好了,它的 thread_rng() 用的 StdRng 就是密码学安全的。
我在 github 上搜了一下 sha1 prng/sha1 rng/sha1 random,Rust 语言这边都没找到什么结果,要自己移植 Java 那边 SHA1PRNG 的实现的话应该要花不少时间的。
-- 是的,否则无法兼容生产上的流量,会解密失败。
用rand和rand_chacha
Cargo.toml
结果:
都与Java的不一致:
结果:
一定要同样的算法吗?只是(密码学安全地)随机产生 key 的话,感觉用 rand 库就好了,它的 thread_rng() 用的 StdRng 就是密码学安全的。
我在 github 上搜了一下 sha1 prng/sha1 rng/sha1 random,Rust 语言这边都没找到什么结果,要自己移植 Java 那边 SHA1PRNG 的实现的话应该要花不少时间的。
即:找到java.security.SecureRandom在Rust中基于种子的同样算法实现即可
非常感谢,你这个方法没问题。再你的这个方法提示下,我找到了问题根源,但是没找到rust里aes+ecb如何代替。
我的旧的程序Java生成key的方式是这样的,我继续再找找看:
只所以不采用你列的这种方式,是因为我要兼容生产已经采用的上面方式生成Key:
这样就需要在rust里找到一种与java上面生成key一致的方法库
--
👇
TinusgragLin: 我这边测试了一下加密,结果一致,都是 b15f0b20af79086a71ea2f59e96636d75759573616d6de356b9ee6708bf2f18e:
aes192, cbc, pkcs5, 仅供参考:
我这边测试了一下加密,结果一致,都是 b15f0b20af79086a71ea2f59e96636d75759573616d6de356b9ee6708bf2f18e:
加解密和JAVA都不熟,提一点小的看法。 你对比HAVA和RUST的时候,先不要去解密,你只做加密,看看两边结果是否能一致。另外十六进制字符串应该是无所谓有没有符号的。