AES 256-CBC上的密钥和iv问题

时间:2017-06-12 10:21:31

标签: java android python-2.7 cryptography aes

我从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&*(

当有人可以使用其他图书馆解决此问题时,请通知我。

1 个答案:

答案 0 :(得分:4)

有4个问题:

  1. python输出和java输入之间的区别
  2. 不同的IV和密钥
  3. 不同的密钥创建
  4. 填充
  5. 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和键进行输入和输出,所以你应该从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)最后,您不需要在"AES/CBC/PKCS5Padding"的java中指定填充,因为这样可以强制Cipher自动填充。

    您可以在"AES/CBC/NoPadding"方法中使用decrypt(),因此它应如下所示:

    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