【发布时间】:2017-08-22 11:36:19
【问题描述】:
我在 Android 下使用 BouncyCastle(技术上是 SpongyCastle)使用 256 位 AES(CBC 模式)加密了一个字符串,并记录了密文、密钥和 iv 的 base64 编码值。
然后,我编写了一个测试程序(作为检测的 Android 单元测试),将所有三个值硬编码为 base64 编码的字符串。当我在Android下运行下面的代码时,它解密成功。
完全相同的 base64 编码密文、密钥和 iv 在 PHP (5.6.30) 下失败。 我很难理解为什么。在这两种情况下,密文、密钥和 iv 都以具有完全相同值的硬编码 base64 编码字符串开始。
除其他外,它抱怨密钥长度(32 字节)无效(我上次检查时,32 * 8 == 256)。
Android (SpongyCastle) 代码:
@Test
public void crossplatformTest() {
String ciphertext64 = "gfcC6t1BarndpzMuvYj2JFpWHqlWSJMhTtxPN7QjyEg=";
String key64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=";
String iv64="AAECAwQFBgcICQoLDA0ODw==";
byte[] ciphertext = Base64.decode(ciphertext64, Base64.DEFAULT);
byte[] key = Base64.decode(key64, Base64.DEFAULT);
byte[] iv = Base64.decode(iv64, Base64.DEFAULT);
byte[] decrypted = Crypto.decryptWithAesCBC(ciphertext, key, iv);
// the following assertion succeeds.
assertEquals("Ugh! Endless grief!", new String(decrypted, StandardCharsets.UTF_8));
}
PHP (OpenSSL) 代码:
<?php
$ciphertext64 = "gfcC6t1BarndpzMuvYj2JFpWHqlWSJMhTtxPN7QjyEg=";
$key64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=";
$iv64="AAECAwQFBgcICQoLDA0ODw==";
// UPDATE: THE LINE BELOW IS THE PROBLEM. SEE ANSWER FOR SOLUTION.
$decrypted = openssl_decrypt($ciphertext64, 'aes-256-cbc', $key64, 0, $iv64);
if ($decrypted == false) {
while ($msg = openssl_error_string())
echo "<p>$msg</p>\n";
echo("key length=" . strlen(base64_decode($key64, true))."<BR>\n");
echo("iv length=" . strlen(base64_decode($iv64, true))."<BR>\n");
}
PHP 的输出:
error:0607A082:digital envelope routines:EVP_CIPHER_CTX_set_key_length:invalid key length
error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
key length=32
iv length=16
根据 php.net (http://php.net/manual/en/function.openssl-decrypt.php) 上似乎存在关于 openssl_decrypt 的少量文档,openssl_decrypt 想要密文、密钥和 iv 的 base64 编码字符串。
对于它的价值,这是我用来加密原始字符串的代码,以及我在 Android 上用来解密它的代码:
public static byte[] decryptWithAesCBC(byte[] ciphertext, byte[] key, byte[] iv) {
try {
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key), iv);
aes.init(false, ivAndKey);
return cipherData(aes, ciphertext);
}
catch (InvalidCipherTextException e) {
throw new RuntimeException(e);
}
}
private static byte[] cipherData(PaddedBufferedBlockCipher cipher, byte[] data) throws InvalidCipherTextException {
int minSize = cipher.getOutputSize(data.length);
byte[] outBuf = new byte[minSize];
int length1 = cipher.processBytes(data, 0, data.length, outBuf, 0);
int length2 = cipher.doFinal(outBuf, length1);
int actualLength = length1 + length2;
byte[] ciphertext = new byte[actualLength];
for (int x=0; x < actualLength; x++) {
ciphertext[x] = outBuf[x];
}
return ciphertext;
}
public static byte[] encryptWithAesCBC(byte[] plaintext, byte[] key, byte[] iv) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "SC");
PaddedBufferedBlockCipher aes = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()));
CipherParameters ivAndKey = new ParametersWithIV(new KeyParameter(key), iv);
aes.init(true, ivAndKey);
return cipherData(aes, plaintext);
}
catch (NoSuchAlgorithmException | NoSuchProviderException | NoSuchPaddingException | InvalidCipherTextException e) {
throw new RuntimeException(e);
}
}
【问题讨论】:
-
阅读文档 php.net/manual/es/function.openssl-decrypt.php 中的 cmets,看起来 IV 和密码应该是二进制的(不是 base64)。我猜你首先需要
base64_decode -
嗯...我从第一条评论中得到了相反的印象,它说“$data 可以是描述中所说的 raw 或 base64。如果没有设置 $option(这是,如果值为0 传入此参数),数据将被假定为 base64 编码。"
-
是的,很混乱,但是在第二条评论中,作者明确表示参数字符串$password必须是二进制形式
标签: aes php-openssl