首页 > 解决方案 > Android 客户端,Python 服务器 - AES + RSA 加密 - 结果:垫块损坏

问题描述

我正在尝试从服务器发送/接收加密消息。服务器使用 Python,而 Android 使用 Java。我正在执行以下操作:

  1. 向服务器发送 RSA 公钥
  2. 使用 AES 生成(密钥 + IV)和加密基本数据
  3. 使用收到的 RSA 公钥加密 (key + IV) (as JSON)
  4. 通过套接字将 RSA 加密的 AES 对发送到客户端
  5. 发送 AES 加密数据
  6. 接收到 AES 对时,用 RSA 私钥解密,接收到的数据匹配
  7. 尝试使用收到的 AES 密钥解密 AES 数据时失败。

在这两个平台上尝试使用两者进行加密/解密时,它都可以正常工作。我相信我从 JSON 中获取 AES(密钥 + IV)的方式有问题,但这是我多次发现的。

失败:

    I/System.out: MESSAGE: {"key": "bgOQ0c9xJVI60BrLUzSGK8IDyM9sVrdd", "iv": "2FbQMXqMrd4fDR4V"}
W/System.err: javax.crypto.BadPaddingException: pad block corrupted
        at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(BaseBlockCipher.java:1337)
W/System.err:     at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:1170)
        at javax.crypto.Cipher.doFinal(Cipher.java:2055)
        at com.example.tests.encrypt.AES.decrypt(AES.java:62)
        at com.example.tests.encrypt.AES.decrypt(AES.java:50)
        at com.example.tests.server.Server$ReceivingThread.run(Server.java:66)
        at java.lang.Thread.run(Thread.java:764)

这是一些代码。

爪哇:

接收数据:

byte[] in  = Base64.decode(fromServerStream.readLine(), Base64.DEFAULT);
byte[] in2 = Base64.decode(fromServerStream.readLine(), Base64.DEFAULT);
if (in != null && in2 != null) {
    String keys = RSA.decrypt(in);
    System.out.println("MESSAGE: " +keys);
    String msg2 = AES.decrypt(in2, keys);
    System.out.println(msg2);
}

生成密钥:

public static void generateKeyPair() throws NoSuchAlgorithmException {
    KeyPairGenerator kpg = KeyPairGenerator.getInstance(Consts.RSA);
    kpg.initialize(Consts.RSASize);
    KeyPair kp = kpg.genKeyPair();
    publicKey = kp.getPublic();
    privateKey = kp.getPrivate();
}

将 PubKey 作为字符串获取(目前仅使用案例“PEM”):

public static String getPublicKey(String option) {
    switch (option) {
        case "PEM":
            String pkcs1pem = "-----BEGIN RSA PUBLIC KEY-----\n";
            pkcs1pem += Base64.encodeToString(publicKey.getEncoded(), Base64.DEFAULT);
            pkcs1pem += "-----END RSA PUBLIC KEY-----";
            return pkcs1pem;

        case "pkcs8-pem":
            String pkcs8pem = "-----BEGIN PUBLIC KEY-----\n";
            pkcs8pem += Base64.encodeToString(publicKey.getEncoded(), Base64.DEFAULT);
            pkcs8pem += "-----END PUBLIC KEY-----";
            return pkcs8pem;

        default:
            return Base64.encodeToString(publicKey.getEncoded(), Base64.DEFAULT).replace("\n", "\\n");
    }
}

RSA解密:

public static String decrypt(byte[] result)
        throws NoSuchAlgorithmException,
        NoSuchPaddingException,
        InvalidKeyException,
        IllegalBlockSizeException,
        BadPaddingException {

    Cipher cipher1 = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
    cipher1.init(Cipher.DECRYPT_MODE, privateKey);
    return new String(cipher1.doFinal(result), Consts.charsetEncoding);
}

AES解密:

public static String decrypt(byte[] plaintext, String JSONKey) throws Exception
{
    JSONObject jsonObject = new JSONObject(JSONKey);
    String key = jsonObject.getString("key");
    String iv  = jsonObject.getString("iv");
    return AES.decrypt(
            plaintext,
            new SecretKeySpec(key.getBytes(Consts.charsetEncoding), Consts.AES),
            iv.getBytes(Consts.charsetEncoding));
}

public static String decrypt(byte[] cipherText, SecretKey key, byte[] IV)
{
    try {
        Cipher cipher = Cipher.getInstance(Consts.AES);
        IvParameterSpec ivSpec = new IvParameterSpec(IV);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        return new String(cipher.doFinal(cipherText), Consts.charsetEncoding);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

缺点:

    //  ENCODING
    public static final String  encoding            = "ASCII";
    public static final Charset charsetEncoding     = StandardCharsets.US_ASCII;


    //  AES
    public static final String  AES                 = "AES";
    public static final int     AESSize             = 256;
    public static final int     IVSize              = 16;

==================================================== ==============

Python / 服务器端:

首先输出:

'{"type": "keyExchange", "pubKey": "-----BEGIN RSA PUBLIC KEY-----\\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjfqo/3Cqw28s8AuXq1/keHgPzcrthSvy\\npepeuu3RANiORN2MIjQjF+KEQPsdNphtBvHA/v4l/9MmrcRFZlLf+1KXnjMWSkr+0lnpVpu7x7Mf\\nnP0RBSS5I5wpxq7FmaTaIGIdDk5G7SZVLChdXDXtpoU95UJ/VLyJe7X17N3nYPIAPxsOMqzKZFN1\\n1EbgmyeSWkU6y5OA3Txggd6csjKq+BGDaVsex50PdS7bP43j2r/+wRb5zDhXI9nHsG6Sb49D1TE4\\ncJksRzkKc1NuBzyY4qmq/UQDdDSowDavEF2hf6zU9xo4o5rvIgGAF+ZHUUMS7qixmx4Vln88WnbW\\nDWCD8J7MRprJmb6zN8YgePQPzbf1527oBW9jCi9OFI7+FPKZOodvMTR6hZSon8LykVY4K8LifsXK\\n7UWy1DljBDakzWJZGHZv9dyXrCeZNxmYTrIWeh19rMWKMoX/X0mWPVPHzfAxqVnryrWsBBu+ImQP\\nAZvnY3lwvOii11T2sv0pXwk+hUKNiA1DmDF8dPQxTeX0I95Sum1VvDFrNNeEsS4NAfeOvnjIwl4Z\\npR0inRbbw7lZmQHerk4kN/x7NM27eOmi8/y6D3G9/Rf4yqLDewA7Qoe+El5VNW7Zd1Iw4sr8JGmb\\nm75mqcZlYtCaweJxfjmHoL4jvJnq5yjxGFyjI/tslxcCAwEAAQ==\\n-----END RSA PUBLIC KEY-----"}'
KEY: bgOQ0c9xJVI60BrLUzSGK8IDyM9sVrdd
IV: 2FbQMXqMrd4fDR4V
b'ASJzulQs9qX3i3k8zMIvTlaNdwrsy4K0GgD/W3L+seH56uTtJ7FO8Mev0j+hwHuGkUj4UZ56MTolYutPpwgnlYcWlzIFjPgKbAG/MwsYdp+JoTS8bSo9YS8x0+ARhXmLa/mQefGUq/0l1YKkVB/SzXp27ni802c4ApPSkP98xp/IhZZgNQswB0c6Mm29o8MTw1/bv3lV8bhkHLtWVDClz91RKz53jTH6XpAFsLtk90ZgX7hRZSO+jAwFlCkRNLKxqwjVANtRuaWm1Agmf10HwKYBVSzgXLpC8+hT90Eicj/8TOj1HSnE/IUweSz21aQbm101H+VP76mbVofq9ID4Mmc+6a+VP2/DHNHUQO0sJuoNUecoe31UaXjHShLaPY4kFHMloOKcuZUsQdnBoTaak3W68pJ5AmHICcZ689FO2e/6QaUF9YzyP1eB3nQpxP48eR2KlK+I288dycMTpapfNWThWHOa+bEjGobLL26+pk3lixyw56RRd+YZ4Fm+omnnpHdtW3Qn388HzmU9Hz0ho/r88ofpVcdxBICLNBjwRgGt5ArfGo4wNvuS7VceFohqk3foVXgkZmHsakjewiqosSk2gqP0yeux0DRweL5vOMo08kMbGlz/Zr8HWcM7UkDbFcGLALY+sf8yu/3nnv/gw0Z45o7TopRiLW8m0cvKYOk='
b'3JRcYYCjwt8/RSuis6TjdJOpxOV4OzIJODkLRGXJb6d89r+cwgZ9s7kcgR0uUOvR'

接收+发送(测试):

rec = self.connection.recv(Server.Consts.MESSAGE_SIZE).decode()
if not rec:
    break

rec = rec.replace('\n', '\\n')
print(repr(rec))
js = json.loads(rec)
key = MRSA.getKeyFromString(js['pubKey'])
dict = {"type": "keyExchange", "pubKey": "ECHO"}
aeskey, aesiv, aesData = MAES.encrypt(json.dumps(dict))
print("KEY: {}".format(base64.b64decode(aeskey).decode()))
print("IV: {}".format(base64.b64decode(aesiv).decode()))
d = {"key": base64.b64decode(aeskey).decode(), "iv": base64.b64decode(aesiv).decode()}
x = MRSA.encrypt(json.dumps(d), key.publickey())

self.send(x)
self.send(aesData)

整个 AES:

@staticmethod
def generateKey():
    return \
        (''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for x in range(Consts.AESSize))).encode(Consts.encoding), \
        (''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for x in range(Consts.IVSize))).encode(Consts.encoding)

@staticmethod
def padding(data):
    if len(data) % 16 != 0:
        return '{}{}'.format(data, ' ' * (16 - len(data) % 16))
    return data

@staticmethod
def encrypt(data):
    key, iv = MAES.generateKey()
    data = MAES.padding(data)
    return base64.b64encode(key), \
           base64.b64encode(iv), \
           base64.b64encode(AES.new(key, AES.MODE_CBC, iv).encrypt(data.encode(Consts.encoding)))

@staticmethod
def decrypt(data, key, iv):
    return AES.new(base64.b64decode(key), AES.MODE_CBC, base64.b64decode(iv)).decrypt(base64.b64decode(data)).strip().decode()

整个 RSA:

@staticmethod
def generateKey():
    return RSA.generate(Consts.RSASize)

@staticmethod
def getKeyFromString(str):
    return RSA.import_key(bytes(str, encoding=Consts.encoding))

@staticmethod
def encrypt(msg, public):
    return base64.b64encode(PKCS1_OAEP.new(public).encrypt(msg.encode(Consts.encoding)))

@staticmethod
def decrypt(data, keyPair):
    return PKCS1_OAEP.new(keyPair).decrypt(base64.b64decode(data)).decode(Consts.encoding)

最后是 Consts:

encoding            = "ASCII"

AES                 = "AES"
AESSize             = 32
IVSize              = 16

RSA                 = "RSA"
RSASize             = 4096

编辑:如果您遇到任何问题,请阅读 Maarten Bodewes 的回答评论。

标签: pythonandroidencryptionaesrsa

解决方案


在我看来,你有两个主要问题。Const.AES没有指定AES/CBC/PKCS5Padding它在现实中使用 PKCS#7 兼容的填充),相反它可能默认为仅用于 ECB 模式AES

Python 中的填充方法也不执行 PKCS#7 填充,而是使用空格字符:

def padding(data):
    if len(data) % 16 != 0:
        return '{}{}'.format(data, ' ' * (16 - len(data) % 16))
    return data

这不是一个好主意,请改用 PKCS#7 填充。

请注意,整个方案相对不安全,只能与 TLS 传输结合使用。然后它可以用于在客户端安全地存储静态数据。您需要对数据进行身份验证(签名)签名,并确保如果要在不安全的连接上使用填充预言,则不适用 - 设计传输安全性不适合初学者。


推荐阅读