如何从php到java获取相同的加密字符串

问题描述 投票:0回答:1

我们使用以下代码在 php 中完成加密和解密

    protected $key    = 'myKey';
    protected $iv     = 'myIv';
    protected $method = 'AES-256-CBC';
public function decrypt($value, AbstractPlatform $platform=null)
    {
        if (gettype($value) === 'NULL') {
            return null;
        }

        if (!base64_decode($value)) {
            return $value;
        }

        try {
            $key = hash('sha256', $this->key);
            $iv  = substr(hash('sha256', $this->iv), 0, 16);
            return openssl_decrypt(base64_decode($value), $this->method, $key, 0, $iv);
        } catch (\Exception $exception) {
            throw new \Error($exception->getMessage());
        }
    }
public function encrypt($value, AbstractPlatform $platform = null)
    {
        if (gettype($value) === 'NULL') {
            return null;
        }

        try {
            $key = hash('sha256', $this->key);
            $iv  = substr(hash('sha256', $this->iv), 0, 16);
            $encryptValue = openssl_encrypt($value, $this->method, $key, 0, $iv);

            if ($encryptValue) {
                return base64_encode($encryptValue);
            }
        } catch (\Exception $exception) {
            throw new \Error($exception->getMessage());
        }

        return false;
    }

是否可以在java中编写类似的代码并获得与php相同的加密字符串,并使用相同的密钥和iv用java解密它?

我尝试过在java中使用密码,如下所示:

public static String encrypt(String stringToEncrypt){

        try {

            byte[] iv = new byte[16];
            IvParameterSpec ivspec = new IvParameterSpec(iv);
            /* Create factory for secret keys. */
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            /* PBEKeySpec class implements KeySpec interface. */
            KeySpec spec = new PBEKeySpec(KEY.toCharArray(), IV.getBytes(), 65536, 256);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
            /* Retruns encrypted value. */
            return Base64.getEncoder()
                    .encodeToString(cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8)));
        }
        catch (InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e)
        {
            System.out.println("Error occured during encryption: " + e);
        }
        return null;

    }
    public static String decrypt(String stringToDecrypt){

        try {
            byte[] iv = new byte[16];
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            /* Create factory for secret keys. */
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
            /* PBEKeySpec class implements KeySpec interface. */
            KeySpec spec = new PBEKeySpec(KEY.toCharArray(), IV.getBytes(), 65536, 256);
            SecretKey tmp = factory.generateSecret(spec);
            SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
            /* Retruns decrypted value. */
            return new String(cipher.doFinal(Base64.getDecoder().decode(stringToDecrypt)));
        }
        catch (InvalidAlgorithmParameterException | InvalidKeyException | NoSuchAlgorithmException | InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | NoSuchPaddingException e)
        {
            System.out.println("Error occured during decryption: " + e);
        }
        return null;

    }

我也尝试过这个:

public static String encrypt(String stringToEncrypt) {
        try {
            IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes("UTF-8"),0,16);
            SecretKeySpec skeySpec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, ivSpec);
            return Base64.getEncoder()
                    .encodeToString(cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }
public static String decrypt(String encrypted) {
        try {
            IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes("UTF-8"),0,16);
            SecretKeySpec skeySpec = new SecretKeySpec(KEY.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, ivSpec);
            byte[] decypted = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(decypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

但是无论如何,我尝试过的加密字符串与使用 php 方法完成的加密字符串不同。非常感谢!

java php encryption
1个回答
0
投票

PHP 代码执行以下操作:

  • 密钥和 IV 是使用 SHA-256 导出的。 PHP代码的

    hash()
    函数默认返回十六进制编码的数据。由于 AES-256-CBC 规范,密钥被
    隐式
    截断为 32 字节,IV 显式截断为 16 字节。在Java端,可以按如下方式实现:

    MessageDigest md = MessageDigest.getInstance("SHA-256");
    byte[] key = HexFormat.of().formatHex(md.digest("myKey".getBytes(StandardCharsets.UTF_8))).substring(0, 32).getBytes(StandardCharsets.UTF_8);
    byte[] iv = HexFormat.of().formatHex(md.digest("myIv".getBytes(StandardCharsets.UTF_8))).substring(0, 16).getBytes(StandardCharsets.UTF_8);
    IvParameterSpec ivspec = new IvParameterSpec(iv);
    SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
    

    与 PHP 代码相反,Java 代码中的密钥必须显式地缩短为 32 字节,因为此处密钥大小决定 AES 变体(32 字节密钥指定 AES-256)。

    请注意,使用快速摘要进行密钥派生是一个漏洞。相反,专用的密钥派生函数,例如至少应使用 PBKDF2 和随机盐。
    此外,直接使用十六进制编码值作为密钥(和 IV)也是一个漏洞,因为它减少了密钥空间(从每字节 256 个值减少到 16 个值)。此外,根据十六进制数字使用的是大写还是小写,这可能会导致兼容性问题(在这种情况下,这并不重要,因为 Java 和 PHP 默认情况下都应用小写)。
    第三个漏洞是代码无法可靠地防止密钥/IV 对的重用。避免这种情况的一种方法是对每次加密使用随机 IV(如评论中所述)。

  • PHP/OpenSSL 默认应用 PKCS#7 填充。在 Java 方面,必须明确指定(如

    PKCS5Padding
    ):

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
    
  • PHP/OpenSSL 在加密过程中默认执行 Base64 编码(第 4 个参数,

    $options = 0
    )。由于 PHP 代码也显式进行 Base64 编码,因此它被 Base64 编码了两次。在Java端,可以按如下方式实现:

    byte[] ciphertext = cipher.doFinal(stringToEncrypt.getBytes(StandardCharsets.UTF_8));
    byte[] ciphertextB64  = Base64.getEncoder().encode(ciphertext);
    return Base64.getEncoder().encodeToString(ciphertextB64);
    

    这同样适用于解密。这里,密文在解密之前必须经过两次 Base64 解码:

    byte[] ciphertextB64 = Base64.getDecoder().decode(stringToDecrypt);
    byte[] ciphertext = Base64.getDecoder().decode(ciphertextB64);
    return new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8);
    

    请注意,双重 Base64 编码毫无意义且效率低下:它没有任何好处,只会降低性能并增加数据量。


测试:使用发布的密钥材料(

myKey
)和IV材料(
myIv
),改编后的Java代码从明文生成以下密文敏捷的棕色狐狸跳过懒狗

ZlUxZUMrTlNpa2FpWG5RdHdRYUhsUk83a0dvci9Pc0xlSjRXVjJ3b1FZSnRTK1Zpd2NVQkYzSDQ2MlVaQmlpTA==

按照PHP代码。

© www.soinside.com 2019 - 2024. All rights reserved.