Search code examples
javapythonencryptionaessha1

Porting encryption method from Java to Python


I can't receive the same output from my python code, what is my mistake?

i'm not sure but i make mistake in encode and decode process

from Crypto.Cipher import AES
from Crypto import Random
import base64
from hashlib import pbkdf2_hmac
import binascii
import os
import datetime, time

def pad(byte_array):
    BLOCK_SIZE = 16
    pad_len = BLOCK_SIZE - len(byte_array) % BLOCK_SIZE
    return byte_array + (bytes([pad_len]) * pad_len)

key = pbkdf2_hmac(
hash_name = 'SHA1', 
password = b"75820705-2b7a-46dc-b811-0f6ad4ff33af", 
salt = os.urandom(8), 
iterations = 100, 
dklen = 384
)

auth_key = "d4eee068-272a-4aec-9681-5e16dcef6fbd";
timedate = x = datetime.datetime.fromtimestamp(time.time()-1000*10*60)
paylaod = auth_key+"|"+x.strftime('%Y-%m-%dT%H:%M:%S.0000%f')[:-3]+"Z"


cipher = AES.new(key[:32], AES.MODE_CBC, key[32:48])
plain = pad(paylaod.encode("UTF-8"))
encrypted_text = cipher.encrypt( plain )

print (base64.b64encode(encrypted_text).decode("UTF-8"))

this is the working method on java

import javax.crypto.Cipher;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.security.SecureRandom;
import java.util.Arrays;
import java.lang.StringBuilder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Calendar;
import java.util.*;

byte[] bArr = new byte[8];
new SecureRandom().nextBytes(bArr);
byte[] encoded = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(new PBEKeySpec(str2.toCharArray(), bArr, 100, 384)).getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(encoded, 0, 32), "AES");

Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
instance.init(instance.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(Arrays.copyOfRange(encoded, 32, 48)));
byte[] doFinal = instance.doFinal(str.getBytes("UTF-8"));

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byteArrayOutputStream.write(doFinal);
byteArrayOutputStream.write(bArr);
            return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());

and this is the main of java:

public static void main(String[] args)
{       
    String auth_key = "d4eee068-272a-4aec-9681-5e16dcef6fbd";
    SimpleDateFormat var0 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'", Locale.US);
    var0.setTimeZone(TimeZone.getTimeZone("UTC"));
    String payload = auth_key+"|"+var0.format(new Date(Long.valueOf((new Date()).getTime()-1000*10*60))); //random key from /keys endpoint
    String outputVal = a(payload, "bd1676b5-5ce3-4351-a39b-36a7b7219c11"); //x-vmob-uid
    System.out.println(outputVal.replace("\n", "").replace(" ", ""));
}

The correct output is this:

Wgxc7xuqdKd2CqyT2KLE6ihankSTbTS/grIj+uyGG4IgpXWFxJ+KE4En/lQnL2vEu67w0sHeT6Tu1ibV0zahqpCKjw4pGPhhuCErS/8pojzg2TSMfFh7fw==

but i receive this:

8/VHDoMCOOI4Aaxus2nxridBPfm4Gvy2g8yRgK3VJUr3eSa3UucsAdzRMapuQj6pN3el12tqaAKYeNpFZCv5SuVosd4AYXwvmf/3uy5yr2U=

hope someone suggest me where to check or give me the error


Solution

  • TL;DR

    The reasons that you see differences in the result of Java vs. Python are:

    1. different values are used in Java vs. Python for pbkdf2_hmac
    2. the current time is used as part of the input, which changes between runs
    3. byteArrayOutputStream.write(bArr); introduces a slighted longer string in Java output.

    My guess is 3 is what you are looking for, but let me put out a long answer of thought process for above conclusions.

    Long answer

    Reproduce the issue

    Complete Java source code that runs:

    package answer;
    
    import java.util.Date;
    import java.util.Locale;
    import java.util.TimeZone;
    import java.util.Arrays;
    
    import java.text.SimpleDateFormat;
    
    import java.io.ByteArrayOutputStream;
    
    import javax.crypto.SecretKeyFactory;
    import javax.crypto.spec.SecretKeySpec;
    import javax.crypto.Cipher;
    import javax.crypto.spec.PBEKeySpec;
    import javax.crypto.spec.IvParameterSpec;
    
    import java.util.Base64;
    
    public class SO56189889 {
        public static void main(String[] args) {
            String auth_key = "d4eee068-272a-4aec-9681-5e16dcef6fbd";
            SimpleDateFormat var0 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSSS'Z'", Locale.US);
            var0.setTimeZone(TimeZone.getTimeZone("UTC"));
    
            // random key from /keys endpoint
            String formatted = var0.format(getSeedDate());
    
            String payload = auth_key + "|" + formatted;
            System.out.println(payload);
            String outputVal = magic(payload, "bd1676b5-5ce3-4351-a39b-36a7b7219c11"); // x-vmob-uid
            System.out.println(outputVal.length());
            System.out.println(outputVal);
        }
    
        public static Date getSeedDate() {
            Date now = new Date(Long.valueOf((new Date()).getTime() - 1000 * 10 * 60));
            return now;
        }
    
        public static String magic(String str, String str2) {
            try {
                byte[] bArr = new byte[8];
                // new SecureRandom().nextBytes(bArr);
                for (int i = 0; i < 8; i++) {
                    bArr[i] = 'X';
                }
                String temp = new String(bArr);
                System.out.println(temp);
    
                byte[] encoded = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
                        .generateSecret(new PBEKeySpec(str2.toCharArray(), bArr, 100, 384)).getEncoded();
                SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(encoded, 0, 32), "AES");
    
                Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
                // XXX: use Cipher.ENCRYPT_MODE (was: instance.ENCRYPT_MODE)
                instance.init(Cipher.ENCRYPT_MODE, secretKeySpec,
                        new IvParameterSpec(Arrays.copyOfRange(encoded, 32, 48)));
                byte[] doFinal = instance.doFinal(str.getBytes("UTF-8"));
    
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byteArrayOutputStream.write(doFinal);
                byteArrayOutputStream.write(bArr);
    
                System.out.println("no exception, everything OK");
    
                return Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray());
            } catch (Exception e) {
                System.out.println(e.toString());
                return "NOT WORKING";
            }
        }
    }
    

    Complete Python code that runs:

    from Crypto.Cipher import AES
    from Crypto import Random
    import base64
    from hashlib import pbkdf2_hmac
    import binascii
    import os
    import datetime, time
    
    def pad(byte_array):
        BLOCK_SIZE = 16
        pad_len = BLOCK_SIZE - len(byte_array) % BLOCK_SIZE
        return byte_array + (bytes([pad_len]) * pad_len)
    
    # salt = os.urandom(8)
    salt = b'XXXXXXXX'
    print(salt)
    print('---------------')
    
    key = pbkdf2_hmac(
    hash_name = 'SHA1', 
    # password = b"75820705-2b7a-46dc-b811-0f6ad4ff33af", 
    password = b"bd1676b5-5ce3-4351-a39b-36a7b7219c11",
    # salt = os.urandom(8), 
    salt = salt,
    iterations = 100, 
    dklen = 384
    )
    
    auth_key = "d4eee068-272a-4aec-9681-5e16dcef6fbd"
    x = datetime.datetime.fromtimestamp(time.time()-1000*10*60)
    timedate = x
    # payload = auth_key+"|" + x.strftime('%Y-%m-%dT%H:%M:%S.0000%f')[:-3]+"Z"
    payload = "d4eee068-272a-4aec-9681-5e16dcef6fbd|1970-01-01T00:00:00.0000000Z"
    print(payload)
    print('-----------------')
    
    cipher = AES.new(key[:32], AES.MODE_CBC, key[32:48])
    plain = pad(payload.encode("UTF-8"))
    encrypted_text = cipher.encrypt(plain)
    
    result = base64.b64encode(encrypted_text).decode("UTF-8")
    print(len(result))
    print(result)
    

    Make sure input of Java program does not change

    In above Java code, the variation come from:

    • getSeedDate()

    • new SecureRandom().nextBytes(bArr); in magic()

    Let's change them:

    public static Date getSeedDate() {
        Date seed = new Date(0L);    // 0L: the milliseconds since January 1, 1970, 00:00:00 GMT.
        return seed;
    }
    
    // new SecureRandom().nextBytes(bArr);
    for (int i = 0; i < 8; i++) {
        bArr[i] = 'X';
    }
    

    Now the Java output is always the same: Z6iTzNaJcDVdL5Rv8psb1D+xakq4By4KUxipmVv0ASjZUfIZO3nu+an5p27BxQ+x1+qoMLgD4vEub5PWcs69FDFy4y2etgiBCiCVnOM6RFlYWFhYWFhYWA==

    Make sure input of Python program does not change

    Change the following so that Python program uses the identical values of the Java program.

    # payload = auth_key+"|" + x.strftime('%Y-%m-%dT%H:%M:%S.0000%f')[:-3]+"Z"
    payload = "d4eee068-272a-4aec-9681-5e16dcef6fbd|1970-01-01T00:00:00.0000000Z"
    
    # salt = os.urandom(8)
    salt = b'XXXXXXXX'
    
    key = pbkdf2_hmac(
    hash_name = 'SHA1', 
    # password = b"75820705-2b7a-46dc-b811-0f6ad4ff33af", 
    password = b"bd1676b5-5ce3-4351-a39b-36a7b7219c11",
    salt = salt,
    iterations = 100, 
    dklen = 384
    )
    

    Note the password needs to be identical with what is in Java.

    Now the Python program always output: Z6iTzNaJcDVdL5Rv8psb1D+xakq4By4KUxipmVv0ASjZUfIZO3nu+an5p27BxQ+x1+qoMLgD4vEub5PWcs69FDFy4y2etgiBCiCVnOM6RFk=

    Compare the results

    Java outputVal.length() is 120, and Python len(result) is 108.

    Let's see them together:

    Java: Z6iTzNaJcDVdL5Rv8psb1D+xakq4By4KUxipmVv0ASjZUfIZO3nu+an5p27BxQ+x1+qoMLgD4vEub5PWcs69FDFy4y2etgiBCiCVnOM6RFlYWFhYWFhYWA==

    Python: Z6iTzNaJcDVdL5Rv8psb1D+xakq4By4KUxipmVv0ASjZUfIZO3nu+an5p27BxQ+x1+qoMLgD4vEub5PWcs69FDFy4y2etgiBCiCVnOM6RFk=

    Try and error

    At this point, i notice that in Java, you have doFinal and bArr,

    byteArrayOutputStream.write(doFinal);
    byteArrayOutputStream.write(bArr);
    

    Whereas in Python, you only use the plain

    plain = pad(payload.encode("UTF-8"))
    encrypted_text = cipher.encrypt(plain)
    
    result = base64.b64encode(encrypted_text).decode("UTF-8")
    

    An experiment shows removing the byteArrayOutputStream.write(bArr); in Java generates the exact string as Python.

    After thoughts

    • make sure the inputs are the same before you compare the results

    • double check the strings you use

    • try-and-error can actually work in some cases