为什么 PHP AES 解密 CBC 模式 iv 可以为空呢? 用 Java 去解密没有 iv 根本解密不出来啊,有什么办法解决?
function h5AesDecode($tradePassword, $skey, $localIV='') {
//$localIV = '0000000000000000';
$encryptKey = $skey;
$result = str_replace("-","+", $tradePassword);
$tradePassword = str_replace("_","/", $result);
//Open module
$module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, $localIV);
mcrypt_generic_init($module, $encryptKey, $localIV);
$encryptedData = base64_decode($tradePassword);
$string = mdecrypt_generic($module, $encryptedData);
$newString = '';
for ($i =0; isset($string[$i]); $i++) {
$asc_code = ord($string[$i]); //得到其asc码
//以下代码旨在过滤非法字符
if ($asc_code == 9 || $asc_code == 10 || $asc_code == 13){
$newString .= ' ';
} else if ($asc_code > 31 && $asc_code != 127) {
$newString .= $string[$i];
}
}
return trim($newString);
}
密文来自前端CryptoJS加密,加密方法如下:
function Encrypt_aes2(word, privateKey) {
var data = word;
var key = CryptoJS.enc.Utf8.parse('1111111111111111');
var iv = CryptoJS.enc.Utf8.parse('');
//加密
var encrypted = CryptoJS.AES.encrypt(data, key, { iv: iv, mode: CryptoJS.mode.CBC });
console.log('encrypted.toString()', encrypted.toString())
// 这里是解密
console.log('解密' ,CryptoJS.AES.decrypt(encrypted.toString(), key, { iv: iv, mode: CryptoJS.mode.CBC }).toString(CryptoJS.enc.Utf8))
return encrypted.toString();
}
public class AesJsUtil {
public static String decrypt(String password, String key) {
ScriptEngine engine = new ScriptEngineManager().getEngineByName("nashorn");
Resource aesJs = new ClassPathResource("js" + File.separator + "aes.js");
try {
engine.eval(new FileReader(aesJs.getFile()));
Invocable invocable = (Invocable) engine;
return (String) invocable.invokeFunction("decrypt", password, key);
} catch (Exception e) {
log.error("JS解密失败", e);
}
}
}
function decrypt(encrypt, currentKey) {
var key = CryptoJS.enc.Utf8.parse(currentKey);
var iv = CryptoJS.enc.Utf8.parse('');
var decrypted = CryptoJS.AES.decrypt(encrypt.toString(), key, { iv: iv, mode: CryptoJS.mode.CBC }).toString(CryptoJS.enc.Utf8);
return decrypted.toString();
}
1
youngs OP 同样的 js 的 CryptoJs 的 iv 也可以为空
|
2
GTim 2020-04-26 18:09:43 +08:00
那是因为人家 PHP 底层调用的是 `openssl` 家的函数,`openssl` 家的函数的默认值就是 16 个 0
|
4
binkcn 2020-04-26 18:27:41 +08:00
@GTim
似乎不是的,参见: https://blog.lancitou.net/how-to-generate-key-and-iv-in-openssl-aes/ OPENSSL AES 算法中的 IV 是这样来的: ``` hash1_128 = MD5(Passphrase + Salt) hash2_128 = MD5(hash1_128 + Passphrase + Salt) hash3_128 = MD5(hash2_128 + Passphrase + Salt) Key = hash1_128 + hash2_128 IV = hash3_128 ``` 另外,@youngs 不知道你用的是 PHP 什么版本,用的哪个函数在解 AES,最好有代码或者伪代码…… |
7
jim9606 2020-04-26 20:34:00 +08:00
CBC 、CFB 、OFB 等流密码模式都是需要 IV 的,不提供的就看默认值是啥了,反正必须要有。
也就 ECB 不需要 IV (千万别用) |
8
rekulas 2020-04-26 22:12:13 +08:00
之前写过 c 版的 aes 虽然加了 iv 但是我不想记录这个 于是每次都是随机 iv 加密 空 iv 解密 解密后抛弃第一个块(因为第一个块无法解密) 后面的就是原始数据
感觉你这问题和这有些关系 你可以使用相同的 iv(例如全 0 值) 打印解密后的原始数据看看一不一样 |
9
xLuoBo 2020-04-26 22:24:45 +08:00
千万不要用 php 的 192 256 aes, key 和 iv 处理位数都和通用库不一样;
他是一只特立独行的猪, 自己加的密只能自己解; 让别人懵逼去吧 |
10
james122333 2020-04-27 06:11:54 +08:00
|
11
youngs OP @rekulas
如果是全 0 的话和 Java 解密结果一致,但是都不是正确的解密结果。php 在 iv 为''的情况应该是有默认值,但是不知道这个默认值是啥 |
12
youngs OP @rekulas 解密出的结果是 SVRQVSSUS***********************91856 确实第一个块解不出来,但是解密出来的密文相比较原来的密文也是查着第一个块的结果,第一个块解密出来是大写字母。。。
|
13
binkcn 2020-04-27 14:55:43 +08:00
@youngs
有个关键问题是:你的密文是用 PHP 加密的吗? 我刚才测试了 PHP 下加密的时候 iv 参数不能为空,即 mcrypt_generic_init() 函数第 3 个参数不能为空,一定要设置的,而根据官网文档的说法是建议设置为全 0 值(长度根据 mcrypt_enc_get_iv_size 来设置)。 所以现在问题的关键是你的密文是怎么加密的,openssl ?还是其他语言? |
14
youngs OP @binkcn 前端用 CryptoJS 加密的
function Encrypt_aes2(word, privateKey) { var data = word; var key = CryptoJS.enc.Utf8.parse('1111111111111111'); var iv = CryptoJS.enc.Utf8.parse(''); //加密 var encrypted = CryptoJS.AES.encrypt(data, key, { iv: iv, mode: CryptoJS.mode.CBC }); console.log('encrypted.toString()', encrypted.toString()) // 这里是解密 console.log('解密' ,CryptoJS.AES.decrypt(encrypted.toString(), key, { iv: iv, mode: CryptoJS.mode.CBC }).toString(CryptoJS.enc.Utf8)) return encrypted.toString(); } |
15
binkcn 2020-04-27 17:11:53 +08:00
@youngs
好了,话题终结了。 看了下 CryptoJs 源码,和我在本帖 4 楼回复的猜想一样,如果你的 key 是个字符串,那么 CryptoJs 底层会用和 Openssl 兼容的函数来生成 iv:根据你的 key 和一个随机的 salt 经过若干次 md5 后生成的,每次 encrypt 的 iv 都不一样,具体方式见我 4 楼的伪代码。 那么,怎么取到 CryptoJS 每次调用 AES.encrypt 时的 iv 呢?很简单…… // Encrypt var ct = CryptoJS.AES.encrypt('my message', 'secret key 123'); var saltHex = ct.salt.toString(); // random salt var ctHex = ct.ciphertext.toString(); // actual ciphertext var ivHex = ct.iv.toString(); // generated IV console.log(saltHex); console.log(ctHex); console.log(ivHex); 你可以试试用打印出来的 iv 到 PHP 或者 Java 里面去解解看。 |
16
binkcn 2020-04-27 17:30:40 +08:00
完整的加密、解密,以及显示本次加密的 iv,在我这里测试是通过的。祝你好运:)
// Encrypt var ct = CryptoJS.AES.encrypt('my message', 'secret key 123'); var saltHex = ct.salt.toString(); // random salt var ctHex = ct.ciphertext.toString(); // actual ciphertext var ivHex = ct.iv.toString(); // generated IV console.log(saltHex); console.log(ctHex); console.log(ivHex); // Decrypt var bytes = CryptoJS.AES.decrypt(ct.toString(), 'secret key 123', { iv: ct.iv, mode: CryptoJS.mode.CBC }); var originalText = bytes.toString(CryptoJS.enc.Utf8); console.log(originalText); |
18
binkcn 2020-04-27 18:26:46 +08:00
@youngs 你前端用 CryptoJS 加密的时候,自己额外存一下本次加密时产生的 iv,回头用 java 解密的时候传进去就行了,这样最简单。
如果,你现在想解密之前已经加密出来的密文,那么就稍微麻烦一些: 首先,加密出来的密文是 base64 的,你 base64_decode 之后会发现开头是 “Salted__”这样的,然后紧跟着的 8 个字节就是 salt 了,然后根据我 4 楼的伪代码,Passphrase (即 key ) 和 Salt 你都有了,可以自己算出 iv 。 这个部分你自己实现即可,18 点过了,我下班回家了…… 2333 |