1
votes

I'm trying to send/receive encrypted messages from and to the server. The server is using Python, while Java for the Android. I'm doing the following:

  1. Sending RSA Public key to the server
  2. Generating (key + IV) and Encrypting basic data with AES
  3. Encrypting the (key + IV) (as JSON) with the received RSA Public key
  4. Sending the RSA encrypted AES pair over the socket to the client
  5. Sending the AES encrypted data
  6. When receiving the AES pair, decrypting it with the RSA private key, the received data matches
  7. When trying to decrypt the AES data with the received AES key it is failing.

On both platforms when trying to encrypt/decrypt with both it is working flawlessly. I believe there is something with the way I get the AES (key + IV) from the JSON, but that is what I found multiple times.

The failure:

    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)

Here is some code.

JAVA:

Receiving the data:

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);
}

Generating Keys:

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();
}

Getting PubKey as String (using case "PEM" only at moment):

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 Decrypting:

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 Decrypting:

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;
}

Consts:

    //  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 / ServerSide:

Firstly the output:

'{"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'

Receive + Send (testing):

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)

Whole 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()

Whole 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)

And finally the Consts:

encoding            = "ASCII"

AES                 = "AES"
AESSize             = 32
IVSize              = 16

RSA                 = "RSA"
RSASize             = 4096

EDIT: If you are having any trouble, please read the comments of the answer from Maarten Bodewes.

1

1 Answers

2
votes

Seems to me that you there are two major problems. Const.AES doesn't specify AES/CBC/PKCS5Padding (which uses PKCS#7 compatible padding in reality), instead it likely defaults to ECB mode for just AES.

The padding method in Python doesn't perform PKCS#7 padding either, instead it uses space characters:

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

This isn't a good idea, use PKCS#7 padding instead.

Note that the entire scheme is relatively insecure and should only be used in combination with TLS transport. Then it can be used to securely store data at rest on the client. You need to authenticate (sign) sign the data and make sure that padding oracles don't apply if this is to be used over an insecure connection - designing transport security is not for the uninitiated.