0

I am trying to do integration with a Bank API. Here is the Java sample code provided by the bank to do encryption/decryption.

package com.example.restservice;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.spec.AlgorithmParameterSpec;
import java.util.Base64;

public class EncHelper {

    public static String encrypt(String key, String enc)  throws Exception {
        // TODO Auto-generated method stub
        byte[] iv1 = new byte[] { (byte) 0x81, 0x12, 0x39, (byte) 0x9C,
                0x07, 0x72, 0x6F, 0x5A, (byte) 0x8E, 0x12, 0x39,
                (byte) 0x9C, 0x17, 0x71, 0x61, 0x5A };

        StringBuilder sb = new StringBuilder();
        for (byte b : iv1) {
            sb.append(String.format("%02X", b));
        }
        System.out.println(sb.toString());
        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv1);

        SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"),
                "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(1, skeySpec, paramSpec);

        byte[] encrypted = cipher.doFinal(enc.getBytes());
        String base64encodedString = Base64.getEncoder().encodeToString(encrypted);
//            System.out.println(base64encodedString);
        return base64encodedString;

    }


    public static String decrypt(String key, String encrypted) throws Exception {
        byte[] keyAsB = key.getBytes("UTF-8");
        SecretKeySpec skeySpec = new SecretKeySpec(keyAsB,
                "AES");
        byte[] iv1 = new byte[] { (byte) 0x81, 0x12, 0x39, (byte) 0x9C,
                0x07, 0x72, 0x6F, 0x5A, (byte) 0x8E, 0x12, 0x39,
                (byte) 0x9C, 0x17, 0x71, 0x61, 0x5A };
        AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv1);

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(2, skeySpec, paramSpec);

        byte[] encBytes = Base64.getDecoder().decode(encrypted.getBytes());

        byte[] original = cipher.doFinal(encBytes);

        return new String(original);
    }
}

Python code I have written is

from Crypto.Cipher import AES
import base64
from Crypto import Random


aes_mode = AES.MODE_CBC

BS = 16

pad = lambda s: bytes(s + (BS - len(s) % BS) * chr(BS - len(s) % BS), 'utf-8')
unpad = lambda s : s[0:-ord(s[-1:])]




class AESCipher:

    def __init__( self, key, key_is_hex=True):
        self.size = len(key)
        if key_is_hex:
            self.key = bytes.fromhex(key) 
        else:
            self.key = bytes(key, 'utf-8')

    def encrypt( self, raw, padData=True):
        raw = pad(raw)
        iv = Random.new().read( AES.block_size )
        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[:16]
        cipher = AES.new(self.key, AES.MODE_CBC, iv )
        d = cipher.decrypt( enc[16:])
        return unpad(d).decode('utf8') 

If I use the java program and do encryption, I am not able to correctly decrypt using my python code

Here is the java code for encryption

EncHelper.encrypt("skeyskeyskeyskey", "ThisTextIsBeingEncryptedHere")

This outputs +CPCZOmJ67d7rwtt/afUaPciCfcUTZtJaFMCctnh2Qs=

cipher1 = AESCipher('skeyskeyskeyskey', False)
x = cipher1.decrypt("+CPCZOmJ67d7rwtt/afUaPciCfcUTZtJaFMCctnh2Qs=")
print(x)

This outputs ncryptedHere instead of ThisTextIsBeingEncryptedHere

basically I am losing some chars when doing the decryption on my end.

What is the issue here?

Ranjith Ramachandra
  • 10,399
  • 14
  • 59
  • 96

1 Answers1

3

The AES mode in use is 'CBC' that requires an Initialization Vector ('iv'). On Java-side the bank API uses a fixed iv (that is UNSECURE) that is used for encryption and decryption, but it is NOT concatenated with the ciphertext on encryption side.

Your Python code is using a RANDOM iv (that is good) but it is concatenated with the ciphertext (iv|ciphertext). On decryption side you grab the iv and decrypt the rest - that's the reason why some text seems to miss.

Solution: use the same static iv for your PYTHON code (as on Java side) and decrypt the complete ciphertext.

Michael Fehr
  • 5,827
  • 2
  • 19
  • 40
  • thanks a lot. I spent too long looking at this and did not realise this was because the IV was hardcoded. Man! So basically the IV shared in the sample code is not IV but it is actually some hardcoded value in their source code. – Ranjith Ramachandra Aug 11 '20 at 10:45
  • 1
    Also, the Java code obtains the key from a string by UTF-8 decoding it. That means the key will almost certainly have substantially less entropy than it should, probably a little more than 16-bits less. – President James K. Polk Aug 11 '20 at 12:48