如何从 Java 中的 openssl_encrypt (PHP) 中解密字符串?

How to decrypt string from openssl_encrypt (PHP) in Java?

我正在尝试在 Java 中复制以下 PHP class 来解密在 PHP 中使用 openssl_encrypt 加密的字符串。

class Encryption {
    /* Encryption method */
    protected $method = 'aes-128-ctr';
    /* Encryption key */
    private $key;

    /* Property constructor */
    public function __construct($key = FALSE, $method = FALSE){
        /* Check if custom key provided */
        if(!$key) {
            /* Set default encryption if none provided */
            $key = '1234567891234567';
        }
        /* Check for control characters in key */
        if(ctype_print($key)) {
            /* Convert ASCII keys to binary format */
            $this->key = openssl_digest($key, 'SHA256', true);
            
        } else {
            $this->key = $key;
        }
        $this->key = $key;
        /* Check for custom method */
        if($method) {
            /* If it is a valid openssl cipher method, use it */
            if(in_array(strtolower($method), openssl_get_cipher_methods())) {
                $this->method = $method;
            /* If it is not, unrecognised method */
            } else {
                die(__METHOD__ . ": unrecognised cipher method: {$method}");
            }
        }
    }
    /* Get iv bytes length */
    protected function iv_bytes(){
        /* Get length of encryption cipher initialisation vector */
        return openssl_cipher_iv_length($this->method);
    }

    /* Encryption method */
    public function encrypt($data) {
        /* Get initialisation vector binary */
        $iv = openssl_random_pseudo_bytes($this->iv_bytes());
        /* Return IV hex & encryption string */
        return bin2hex($iv) . openssl_encrypt($data, $this->method, $this->key, OPENSSL_ZERO_PADDING, $iv);
    }

    /* Decrypt encrypted string */
    public function decrypt($data){
        /* Get IV string length */
        $iv_strlen = 2  * $this->iv_bytes();
        /* Parse out the encryption string and unpack the IV and encrypted string. $regs is passed by reference in preg_match() */
        if(preg_match("/^(.{" . $iv_strlen . "})(.+)$/", $data, $regs)) {

            list(, $iv, $crypted_string) = $regs;
            print_R($regs);
            /* If there are character representing a hex and the IV string length is divisible by 2 */
            if(ctype_xdigit($iv) && (strlen($iv) % 2 == 0)) {
                /* Decrypt the unpacked encrypted string */
                return openssl_decrypt($crypted_string, $this->method, $this->key, OPENSSL_ZERO_PADDING, hex2bin($iv));
            }
        }

        return FALSE; // failed to decrypt
    }

}

这是 Java

中解密器的当前沙箱 class
public class PHPDecryptor {
    
    private static String removeIVHash(String data) {
        /* IV string length is 128 bit (16 bytes) * 2 */
        int iv_strlen = 2 * 16;
        /* Remove IV hash */
        String encrypted_string = data.substring(iv_strlen);

        return encrypted_string;
    }
    
    
    public static String decrypt(String data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException  {
        
        String encrypted_string = removeIVHash(data);
        
        /* Get bytes for string and key */
        byte[] text_to_decrypt = encrypted_string.getBytes(StandardCharsets.UTF_8);
        String key_string = "1234567891234567";
        byte[] key = key_string.getBytes(StandardCharsets.UTF_8);
        
        /* Get secret key and iv */
        SecretKeySpec secret_key = new SecretKeySpec(key, "AES");
        IvParameterSpec iv = new IvParameterSpec(new byte[16]);
        
        /* Init cipher */
        Cipher cipher = Cipher.getInstance("AES/CTR/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secret_key, iv);
        
        /* Decrypt and cast to string */
        byte[] decrypted = cipher.doFinal(text_to_decrypt);
        String result = new String(decrypted, StandardCharsets.UTF_8);

        return result;
    }
    
    public static void main(String[] arg) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
        String text_to_decrypt = "203790144a345320d98fb773795d518e/ioQTApeVMV/4g=="; // Encrypted by PHP
        System.out.println(decrypt(text_to_decrypt));
    }
}

但是,我没有得到准确的解密 - 事实上 Java class 的 return 结果完全是胡说八道,包含无效的 UTF-8 字符。

这里有没有明显的错误?

您似乎没有对 IV 做任何事情,只是将其删除。

从您删除的 IV 创建一个 IVParameterSpec 实例。

public static IvParameterSpec getIv(String iv) {
    return new IvParameterSpec(iv.getBytes(StandardCharsets.UTF_8));
}

如果你使用一些分隔符(我喜欢用“:”)来分隔 iv 和其余的密文,你可以更好地从字符串中检索 IV,这样你就不需要担心十六进制 IV 长度并像这样检索它:

 private static String decodeIvString(String data) {
   
    String[] parts = data.split(":");
    String iv = decodeHex(parts[0]);
    return iv;
}

显然,您必须在 php 的加密函数中添加定界符。 然后您可以将 IV 传回 getIv() 函数。

    /* Encryption method */
public function encrypt($data) {
    /* Get initialisation vector binary */
    $iv = openssl_random_pseudo_bytes($this->iv_bytes());
    /* Return IV hex & encryption string */
    return bin2hex($iv) . ":" . openssl_encrypt($data, $this->method, $this->key, OPENSSL_ZERO_PADDING, $iv);
}

并且正如上面提到的评论,您应该将 PKCS5Padding 替换为 NoPadding

这是我用来解密的一段实用代码:

public static byte[] decryptSymmetric(Algorithm algorithm, Key key, IvParameterSpec iv, byte[] data) throws CryptoFailure {
    try {
        Security.addProvider(new BouncyCastleProvider());
        Cipher aes = Cipher.getInstance(algorithm.getMode(), BouncyCastleProvider.PROVIDER_NAME);
        aes.init(Cipher.DECRYPT_MODE, key, iv);
        return aes.doFinal(data);
    } catch (BadPaddingException | IllegalBlockSizeException e) {
        throw new CryptoFailure(e);
    } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
        throw new CryptoError(e);
    }
}