我在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
根据openssl_decrypt的哪些文档似乎存在于php.net(http://php.net/manual/en/function.openssl-decrypt.php),openssl_decrypt想要使用base64编码的字符串进行密文,密钥和iv。
对于它的价值,这里是我用来加密原始字符串的代码,以及我在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);
}
}
答案 0 :(得分:0)
好的,找到了明确的答案&amp;确认它有效。
php.net上openssl_decrypt的官方文档很糟糕,而且单独发表的前几条评论介于“不完整”和“误导”之间。
根据openssl_decrypt最权威的文档(在PHP 5.6.30本身的源代码中为ext / openssl / openssl.c):
除非明确指定OPENSSL_RAW_DATA作为选项,否则$ data被假定为base64编码。源代码本身清楚地说明了这一点:
if (!(options & OPENSSL_RAW_DATA)) {
base64_str = (char*)php_base64_decode((unsigned char*)data, data_len, &base64_str_len);
if (!base64_str) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to base64 decode the input");
RETURN_FALSE;
}
data_len = base64_str_len;
data = base64_str;
}
HOWEVER ,$ password和$ iv的值直接转换为(unsigned char *)。
因此,正确的用法是:
$ciphertext64 = "gfcC6t1BarndpzMuvYj2JFpWHqlWSJMhTtxPN7QjyEg=";
$key64 = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8=";
$iv64="AAECAwQFBgcICQoLDA0ODw==";
$key = base64_decode($key64, true);
$iv = base64_decode($iv64, true);
$decrypted = openssl_decrypt($ciphertext64, 'aes-256-cbc', $key, 0, $iv);
...输出我在Android上使用BouncyCastle(SpongyCastle)最初加密的字符串的值 - “唉!无尽的悲伤!”
至于为什么官方文档如此糟糕,PHP的开发人员显然决定悄悄地弃用openssl函数,期望一旦PHP 7.2成为主流,每个人都将使用libsodium。将libsodium添加到PHP 7.2以后的默认安装是很棒的,但不幸的是,它不会对共享的Web托管帐户做任何人(谁缺少管理员,更不用说root,shell访问服务器而无法安装自己的延伸至少要到2018年初才能好转。
换句话说,如果您在2017年末之后的某个时间通过Google发现此帖,同时尝试解决您使用openssl和PHP时遇到的一些问题,请检查您的网络服务器是否具有PHP 7.2。
如果没有,既没有升级到PHP 7.2+也没有在服务器上安装libsodium是可行的选择,请继续我的解决方案。
如果确实如此,请忘记openssl_ *功能曾经存在,并直接进入libsodium(这设计使得意外搞砸更加困难)