PHP 的 AES 加密问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
youngs
V2EX    程序员

PHP 的 AES 加密问题

  •  1
     
  •   youngs 2020-04-26 17:55:04 +08:00 3503 次点击
    这是一个创建于 2007 天前的主题,其中的信息可能已经有所发展或是发生改变。

    为什么 PHP AES 解密 CBC 模式 iv 可以为空呢? 用 Java 去解密没有 iv 根本解密不出来啊,有什么办法解决?

    第 1 条附言    2020-04-26 18:32:11 +08:00
    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); } 
    第 2 条附言    2020-04-27 15:51:24 +08:00

    密文来自前端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(); } 
    第 3 条附言    2020-05-07 11:34:02 +08:00
    ## 无奈的最终办法:JAVA 调用 JS
    ### java 代码:
    ```
    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);
    }
    }
    }
    ```
    ### aes.js 代码:
    ```
    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();
    }
    ```
    第 4 条附言    2020-05-07 11:34:21 +08:00

    无奈的最终办法:JAVA调用JS

    java代码:

    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); } } } 

    aes.js代码:

    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(); } 
    19 条回复    2020-04-27 18:28:10 +08:00
    youngs
        1
    youngs  
    OP
       2020-04-26 17:59:56 +08:00
    同样的 js 的 CryptoJs 的 iv 也可以为空
    yemoluo
        2
    yemoluo  
       2020-04-26 18:09:43 +08:00
    那是因为人家 PHP 底层调用的是 `openssl` 家的函数,`openssl` 家的函数的默认值就是 16 个 0
    youngs
        3
    youngs  
    OP
       2020-04-26 18:18:12 +08:00
    @GTim 可是给 iv 赋 16 个 0,解密出来的不是原来的明文
    binkcn
        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,最好有代码或者伪代码……
    youngs
        5
    youngs  
    OP
       2020-04-26 18:32:50 +08:00
    @binkcn
    代码已经加上了
    yemoluo
        6
    yemoluo  
       2020-04-26 19:25:14 +08:00
    @GTim CBC 模式是不需要 IV 的,你把你的 Java 代码也贴出来一下
    jim9606
        7
    jim9606  
       2020-04-26 20:34:00 +08:00
    CBC 、CFB 、OFB 等流密码模式都是需要 IV 的,不提供的就看默认值是啥了,反正必须要有。

    也就 ECB 不需要 IV (千万别用)
    rekulas
        8
    rekulas  
       2020-04-26 22:12:13 +08:00
    之前写过 c 版的 aes 虽然加了 iv 但是我不想记录这个 于是每次都是随机 iv 加密 空 iv 解密 解密后抛弃第一个块(因为第一个块无法解密) 后面的就是原始数据

    感觉你这问题和这有些关系 你可以使用相同的 iv(例如全 0 值) 打印解密后的原始数据看看一不一样
    xLuoBo
        9
    xLuoBo  
       2020-04-26 22:24:45 +08:00
    千万不要用 php 的 192 256 aes, key 和 iv 处理位数都和通用库不一样;
    他是一只特立独行的猪, 自己加的密只能自己解; 让别人懵逼去吧
    youngs
        11
    youngs  
    OP
       2020-04-27 10:12:40 +08:00
    @rekulas
    如果是全 0 的话和 Java 解密结果一致,但是都不是正确的解密结果。php 在 iv 为''的情况应该是有默认值,但是不知道这个默认值是啥
    youngs
        12
    youngs  
    OP
       2020-04-27 10:26:40 +08:00
    @rekulas 解密出的结果是 SVRQVSSUS***********************91856 确实第一个块解不出来,但是解密出来的密文相比较原来的密文也是查着第一个块的结果,第一个块解密出来是大写字母。。。
    binkcn
        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 ?还是其他语言?
    youngs
        14
    youngs  
    OP
       2020-04-27 15:50:47 +08:00
    @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();
    }
    binkcn
        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 里面去解解看。
    binkcn
        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);
    youngs
        17
    youngs  
    OP
       2020-04-27 18:11:23 +08:00
    @binkcn 大佬,用 JAVA 怎么取到这个 IV 呢。。。。
    binkcn
        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
    youngs
        19
    youngs  
    OP
       2020-04-27 18:28:10 +08:00
    @binkcn 大佬!我解出来了。。。详细结果 append 到主题上,感谢~
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2718 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 23ms UTC 08:09 PVG 16:09 LAX 01:09 JFK 04:09
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86