2

I have an encrypted text from Java(v8) using AES, which I am trying to decrypt in python using same SecretKey, Salt but I am getting issue while unpading about index out of range. When I do the reverse i.e encrypt in python and decrypt in java then I am able to get the text but with some unwanted prefix.

Following is my java and python code, which I have tried.

Java Code(Base64 from org.apache.commons.codec.binary.Base64)

public static String encrypt(String secretKey, String salt, String value) throws Exception {
        Cipher cipher = initCipher(secretKey, salt, Cipher.ENCRYPT_MODE);
        byte[] encrypted = cipher.doFinal(value.getBytes());
        return Base64.encodeBase64String(encrypted);
    }

    public static String decrypt(String secretKey, String salt, String encrypted) throws Exception {
        Cipher cipher = initCipher(secretKey, salt, Cipher.DECRYPT_MODE);
        byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));
        return new String(original);
    }

    private static Cipher initCipher(String secretKey, String salt, int mode) throws Exception {

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");

        KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), 65536, 256);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKeySpec skeySpec = new SecretKeySpec(tmp.getEncoded(), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(mode, skeySpec, new IvParameterSpec(new byte[16]));
        return cipher;
    }

    public static void main(String[] args) throws Exception {
        String secretKey = "Secret";
        String fSalt = "tJHnN5b1i6wvXMwzYMRk";
        String plainText = "England";

        String cipherText = encrypt(secretKey, fSalt, plainText);
        System.out.println("Cipher: " + cipherText);
//      cipherText = "6peDTxE1xgLE4hTGg0PKTnuuhFC1Vftsd7NH9DF/7WM="; // Cipher from python
        String dcrCipherText = decrypt(secretKey, fSalt, cipherText);
        System.out.println(dcrCipherText);

    }

Python Code(version 3.6) & Pycrypto V2.6

import base64
import hashlib
import os

from Crypto.Cipher import AES

BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)

# unpad = lambda s: s[:-ord(s[len(s) - 1:])]
unpad = lambda s: s[0:-s[-1]]

def get_private_key(secretKey, salt):
    key = hashlib.pbkdf2_hmac('SHA256', secretKey.encode(), salt.encode(), 65536, 32)
    return key


def encrypt(message, salt, secretKey):
    private_key = get_private_key(secretKey, salt)
    message = pad(message)
    iv = os.urandom(BS)  # 128-bit IV
    cipher = AES.new(private_key, AES.MODE_CBC, iv, segment_size=256)
    return base64.b64encode(iv + cipher.encrypt(message))


def decrypt(enc, salt, secretKey):
    private_key = get_private_key(secretKey, salt)
    enc = base64.b64decode(enc)
    iv = enc[:BS]
    cipher = AES.new(private_key, AES.MODE_CBC, iv, segment_size=256)
    return unpad(cipher.decrypt(enc[BS:]))


secretKey = "Secret"
salt = "tJHnN5b1i6wvXMwzYMRk"
plainText = "England"
cipher = encrypt(plainText, salt, secretKey)
print("Cipher: " + bytes.decode(cipher))

# cipher = "0JrZdg9YBRshfTdr1d4zwQ==" # Cipher from java
decrypted = decrypt(cipher, salt, secretKey)
print("Decrypted " + bytes.decode(decrypted))

Java Decrypt output: �U�����or���England when I pass python cipher, expected: England Python Decrypt output: unpad = lambda s : s[0:-s[-1]] IndexError: index out of range, expected: England

I went through other post as well on stack regarding this issue but, it doesn't worked out as they have used different mode.

1
  • Working sample can be found at here Commented Jul 24, 2020 at 10:30

2 Answers 2

3

In python, you're storing the iv (initialization vector) in the first 16 bytes of the encrypted message.

In Java, you're doing no such thing - you're passing an empty IV and you treat the whole message including the first 16 bytes as ciphertext.

You need to make sure that Java and Python match up.

Either you don't use an IV in both, in which case you remove that part in Python.

Or you use an IV in both, in which case you need to update your Java code to generate a random IV on encryption, adding it to the result of the encryption. On decryption, the Java code needs to take the first 16 bytes as the IV and pass it to the Cipher.

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the reply, I understand that IV part is added in python encryption, but when I remove it in python, it generates the cipher which java decrypt method unable to decrypt and throw javax.crypto.BadPaddingException: Given final block not properly padded, and Python decrypt also throw unpad = lambda s: s[0:-s[-1]] IndexError: index out of range And I tried the changes metioned above in java which worked well but I can't change logic in java side as it will impact other feature. So can you provide what exactly I need to change in Python decrypt function?
In Java, you are providing 16 bytes with the value zero as the IV (new byte[16] creates an array of 16 bytes, all of them initialized to zero). So you would need to do the same in python, instead of iv = os.urandom(BS) or iv = enc[:BS] - and you shouldn't store the IV in the encoded message anymore.
I initialized iv = "\x00"*BS and am not adding iv to the cipher, which generates the same cipher as java, but python unable to decrypt it, getting unpad = lambda s: s[0:-s[-1]] IndexError: index out of range. I have removed iv = enc[:BS] in decrypt function. Can you help me what else I need to do, so that it can able to decrypt properly. Thanks again!
Okay finally able to solve by changing return unpad(cipher.decrypt(enc[BS:])) to return unpad(cipher.decrypt(enc)) in python decrypt function.
3

The correct python 3 code using pycryptodome which will match the Java code would be like this:

import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad,pad
import hashlib

def get_private_key(secretKey, salt):
    # _prf = lambda p,s: HMAC.new(p, s, SHA256).digest()
    # private_key = PBKDF2(secretKey, salt.encode(), dkLen=32,count=65536, prf=_prf )
    # above code is equivalent but slow
    key = hashlib.pbkdf2_hmac('SHA256', secretKey.encode(), salt.encode(), 65536, 32)
    # KeySpec spec = new PBEKeySpec(secretKey.toCharArray(), salt.getBytes(), 65536, 256);
    return key

def encrypt(message, salt, secretKey):
    private_key = get_private_key(secretKey, salt)
    message = pad(message.encode(), AES.block_size)
    iv = "\x00"*AES.block_size  # 128-bit IV
    cipher = AES.new(private_key, AES.MODE_CBC, iv.encode())
    return base64.b64encode(cipher.encrypt(message))


def decrypt(enc, salt, secretKey):
    # _prf = lambda p,s: HMAC.new(p, s, SHA256).digest()
    # private_key = PBKDF2(secretKey, salt.encode(), dkLen=32,count=65536, prf=_prf )
    private_key = get_private_key(secretKey, salt)
    enc = base64.b64decode(enc)
    iv = "\x00"*AES.block_size
    cipher = AES.new(private_key, AES.MODE_CBC, iv.encode())
    return unpad(cipher.decrypt(enc), AES.block_size).decode('utf-8')


secretKey = "Secret"
salt = "tJHnN5b1i6wvXMwzYMRk"
plainText = "England"
enc_datta = encrypt(plainText, salt, secretKey)
print(f"Encrypted: {enc_datta}")
# Encrypted: 0JrZdg9YBRshfTdr1d4zwQ==

cipher = "0JrZdg9YBRshfTdr1d4zwQ==" # Cipher from java
decrypted = decrypt(cipher, salt, secretKey)
print(f"Decrypted: {decrypted}" )
# Decrypted: England

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.