我们使用以下代码在 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 方法完成的加密字符串不同。非常感谢!
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代码。