【问题标题】:Issue with key and iv on AES 256-CBCAES 256-CBC 上的密钥和 iv 问题
【发布时间】:2017-06-12 10:21:30
【问题描述】:

我从 Python 得到一个加密的 base64 字符串。

格式是 AES 256 CBC,但是当我尝试使用 Android 解密时,它返回解密后的字符串为 nil。

Python

# coding=utf-8
import base64
from random import choice
from string import letters

try:
    from Crypto import Random
    from Crypto.Cipher import AES
except ImportError:
    import crypto
    import sys

    sys.modules['Crypto'] = crypto
    from crypto.Cipher import AES
    from crypto import Random


class AESCipher(object):
    def __init__(self, key):
        self.bs = 32
        self.key = key

    def encrypt(self, raw):
        _raw = raw
        raw = self._pad(raw)

        print raw, ';'
        print _raw, ';'

        iv = "".join([choice(letters[:26]) for i in xrange(16)])
        print " iv :", iv
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return base64.b64encode(iv + cipher.encrypt(raw))

    def decrypt(self, enc):
        enc = base64.b64decode(enc)
        iv = enc[:AES.block_size]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return self._unpad(cipher.decrypt(enc[AES.block_size:])).decode('utf-8')

    def _pad(self, s):
        a = (self.bs - len(s) % self.bs)
        b = chr(self.bs - len(s) % self.bs)
        return s + a * b

    @staticmethod
    def _unpad(s):
        return s[:-ord(s[len(s) - 1:])]
def encrypt(k, t):
    o = AESCipher(k)
    return o.encrypt(t)


def decrypt(k, t):
    o = AESCipher(k)
    return o.decrypt(t)


def main():
    k = "qwertyuiopasdfghjklzxcvbnmqwerty"
    s1 = "Hello World!"

    d2 = encrypt(k, s1)

    print " Password :", k
    print "Encrypted :", d2
    print "    Plain :", decrypt(k, d2)

if __name__ == '__main__':
    main()

Java

这里我使用https://github.com/fukata/AES-256-CBC-Example

final String aEcodedSting = "aWVnZWphbnBleWJlemdteeAal+cw04QPYRuuIC3J1/zbkZZSCqxGLo/a26ZiieOk";
String decrypted = AESUtil.decrypt(aEcodedSting);

当我尝试解密时,我得到了这个

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.vinu.aessamble/com.example.vinu.aessamble.MainActivity}: 
java.lang.RuntimeException: javax.crypto.BadPaddingException: error:1e06b065:Cipher functions:EVP_DecryptFinal_ex:BAD_DECRYPT

这是 Python 加密输出:

Password : qwertyuiopasdfghjklzxcvbnmqwerty
Encrypted : aWVnZWphbnBleWJlemdteeAal+cw04QPYRuuIC3J1/zbkZZSCqxGLo/a26ZiieOk
iv : iegejanpeybezgmy
plainText : ser456&*(

如果有人可以使用其他库解决此问题,请通知我。

【问题讨论】:

  • 在此处添加代码而不是链接到 github
  • @Jens : 已更新 .. 感谢您的回复
  • 请出示minimal reproducible example,以便我们自己尝试
  • Python 代码正在运行。但是在使用该库时出现 java 代码问题。
  • 1.通过“Python 代码正在运行”,这是否意味着您可以往返加密和解密? 2. 加密“Hello World!”使用 CBC 和前置 IV 应该产生 32 个字节,结果是 48 个字节。看来您并没有像您认为的那样加密。提供ivraw 的打印输出,实际数据被加密。 3. 根据 Jens 的要求,请提供 MCVE。

标签: java android python-2.7 cryptography aes


【解决方案1】:

有4个问题:

  1. python输出和java输入的区别
  2. 不同的 IV 和密钥
  3. 不同的密钥创建
  4. 填充

1) 目前你的python代码输出是iv + encrypted_data的base64编码

return base64.b64encode(iv + cipher.encrypt(raw))

但在 java 中,您直接解密原始数据。

你应该这样解决

// Decode base64
byte[] array = Base64.decode(src);
// Get only encrypted data (removing first 16 byte, namely the IV)
byte[] encrypted = Arrays.copyOfRange(array, 16, array.length);
// Decrypt data
decrypted = new String(cipher.doFinal(encrypted));

2) 您必须使用相同的 IV 和 key 进行输入和输出,因此您应该从 python 控制台输出中复制它们:

iv : qbmocwtttkttpqvv
Password : qwertyuiopasdfghjklzxcvbnmqwerty
Encrypted : anZxZHVpaWJpb2FhaWdqaCK0Un7H9J4UlXRizOJ7s8lchAWAPdH4GRf5tLAkCmm6
    Plain : Hello World!

并粘贴到java代码中:

private static final String ENCRYPTION_KEY = "qwertyuiopasdfghjklzxcvbnmqwerty";
private static final String ENCRYPTION_IV = "qbmocwtttkttpqvv";

3) 在 python 中,您将密钥用作字符串,但在 java 库中,它在用于解密之前经过哈希处理,因此您应该更改 makeKey() 方法:

static Key makeKey() {
    try {
        byte[] key = ENCRYPTION_KEY.getBytes("UTF-8");
        return new SecretKeySpec(key, "AES");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }

    return null;
}

4) 最后,您不需要在 java 中使用 "AES/CBC/PKCS5Padding" 指定填充,因为这样会强制 Cipher 自动填充。

您可以在您的decrypt() 方法中简单地使用"AES/CBC/NoPadding",所以它应该如下所示:

public static String decrypt(String src) {
    String decrypted = "";
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, makeKey(), makeIv());
        byte[] array = Base64.decode(src);
        byte[] encrypted = Arrays.copyOfRange(array, 16, array.length);
        decrypted = new String(cipher.doFinal(encrypted));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return decrypted;
}

带有 base64 和 IV 的 Java 输出:

encrypted: aWVnZWphbnBleWJlemdteeAal+cw04QPYRuuIC3J1/zbkZZSCqxGLo/a26ZiieOk
decrypted: ser456&*(

编辑:

正如Artjom B. 所建议的那样(谢谢),最好直接从密文中读取 IV,而不是在 AESUtil 中进行硬编码。

您的输入由前 16 个字节的 IV 和后 16 个字节的加密文本组成,因此您可以利用这一点。

public static String decrypt(String src) {
    String decrypted = "";
    try {
        Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
        // Decode input
        byte[] array = Base64.decode(src);
        // Read first 16 bytes (IV data)
        byte[] ivData = Arrays.copyOfRange(array, 0, 16);
        // Read last 16 bytes (encrypted text)
        byte[] encrypted = Arrays.copyOfRange(array, 16, array.length);
        // Init the cipher with decrypt mode, key, and IV bytes array (no more hardcoded)
        cipher.init(Cipher.DECRYPT_MODE, makeKey(), new IvParameterSpec(ivData));
        // Decrypt same old way
        decrypted = new String(cipher.doFinal(encrypted));
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return decrypted;
}

此外,正如here所说的

Python 代码使用 32 字节的块大小进行填充,这意味着 Java 仍然无法解密所有可能密文的一半。 AES 块大小为 16 字节,这应该在 Python 实现中更改

您可以如下更改您的 Python 类(AES.block_size 等于 16):

class AESCipher(object):
    def __init__(self, key):
        self.bs = AES.block_size
        self.key = key

【讨论】:

  • 最好自定义 Java 代码以从密文中读取 IV,而不是对其进行硬编码。此外,您错过了 Python 代码使用 32 字节块大小进行填充,这意味着 Java 仍然无法解密所有可能密文的一半。 AES 块大小为 16 字节,这应该在 Python 实现中进行更改。
  • 我同意,我没有改变更多,但它会更好。目前我在移动设备上,所以我无法进行良好的编辑。随意编辑,否则我会按照您的建议尽快进行编辑:)
  • @ArtjomB。已编辑,谢谢
  • @ChristianAscone。这是我在使用 NoPadding 时得到的。 ser456&*( + 附加块。我无法编辑 python 代码。请帮助
  • 我尝试使用您在原始答案中发布的数据。 Base64 aWVnZWphbnBleWJlemdteeAal+cw04QPYRuuIC3J1/zbkZZSCqxGLo/a26ZiieOk 和 iv iegejanpeybezgmy,但我得到的是 ser456&*(。您是否尝试过 Edit 块中的 decrypt() 方法?
猜你喜欢
  • 2013-03-13
  • 2021-04-22
  • 1970-01-01
  • 1970-01-01
  • 2014-01-13
  • 1970-01-01
  • 2013-08-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多