Java – AES encryption done in OpenSSL decrypts successfully, but fails when encrypted in Java

AES encryption done in OpenSSL decrypts successfully, but fails when encrypted in Java… here is a solution to the problem.

AES encryption done in OpenSSL decrypts successfully, but fails when encrypted in Java

I have AES encrypted with the OpenSSL code below and successfully decrypted it on the tax website

openssl rand 48 > 48byterandomvalue.bin
hexdump /bare 48byterandomvalue.bin > 48byterandomvalue.txt

set /a counter=0
for /f "tokens=* delims= " %%i in (48byterandomvalue.txt) do (
set /a counter=!counter!+1
set var=%%i
if "!counter!" =="1" (set aes1=%%i)
if "!counter!" =="2" (set aes2=%%i)
if "!counter!" =="3" (set iv=%%i)
)

set result1=%aes1:~0,50%
set result1=%result1: =%
set result2=%aes2:~0,50%
set result2=%result2: =%
set aeskey=%result1%%result2%
set initvector=%iv:~0,50%
set initvector=%initvector: =%

openssl aes-256-cbc -e -in PAYLOAD.zip -out PAYLOAD -K %aeskey% -iv %initvector%

openssl rsautl -encrypt -certin -inkey test_public.cer -in 
48byterandomvalue.bin -out 000000.00000.TA.840_Key

But as part of the migration, I wanted to do the same thing in Java, so I used javax.crypto and java.security libraries, but decryption failed I uploaded the file to the tax website

//creating the random AES-256 secret key
SecureRandom srandom = new SecureRandom(); 
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
byte[] aesKeyb = secretKey.getEncoded();

creating the initialization vector
byte[] iv = new byte[128/8];
srandom.nextBytes(iv);
IvParameterSpec ivspec = new IvParameterSpec(iv);

byte[] encoded = Files.readAllBytes(Paths.get(filePath));
str = new String(encoded, StandardCharsets.US_ASCII);

fetching the Public Key from certificate
FileInputStream fin = new FileInputStream("test_public.cer");
CertificateFactory f = CertificateFactory.getInstance("X.509");
X509Certificate certificate = (X509Certificate)f.generateCertificate(fin);
PublicKey pk = certificate.getPublicKey();

encrypting the AES Key with Public Key
Cipher RSACipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
RSACipher.init(Cipher.ENCRYPT_MODE, pk);
byte[] RSAEncrypted = RSACipher.doFinal(aesKeyb);

FileOutputStream out = new FileOutputStream("000000.00000.TA.840_Key");
out.write(RSAEncrypted);
out.write(iv);
out.close();

In addition, the AES key generated by java is not the same as that generated by openssl. Can you guys help.

Edit 1:
The following is the AES encryption code used:

Cipher AESCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
AESCipher.init(Cipher.ENCRYPT_MODE, secretKey, ivspec);
byte[] AESEncrypted = AESCipher.doFinal(str.getBytes("UTF-8"));
String encryptedStr = new String(AESEncrypted);

Solution

  • Scripts and Java code are different for data encrypted with RSA:

    The script generates a random sequence of 48 bytes and stores it in the file 48byterandomvalue.bin. The first 32 bytes are used as AES keys and the last 16 bytes are used as IVs. THE KEY AND IV ARE USED TO ENCRYPT THE FILE PAYLOAD .zip IN CBC MODE USING AES-256 AND STORE IT AS A FILE PAYLOAD. The file 48byterandomvalue.bin is encrypted using RSA and stored as a file 000000.00000.TA.840_Key.

    In Java code, generate a random 32-byte AES key and a random 16-byte IV. Both are used to perform encryption using AES-256 in CBC mode. AES keys use RSA encryption, connect to unencrypted IVs, and the results are stored in file 000000.00000.TA.840_Key.

    The

    contents of 000000.00000.TA.840_Key files are different for scripts and Java code. For Java code that uses script logic to generate file 000000.00000.TA.840_Key, the unencrypted AES key must be with the unencrypted IV and this result must be encrypted with RSA:

    ...
    byte[] aesKeyb byte-array with random 32-bytes key
    byte[] iv      byte-array with random 16-bytes iv
    byte[] key_iv = new byte[aesKeyb.length + iv.length];
    System.arraycopy(aesKeyb, 0, key_iv, 0, aesKeyb.length);
    System.arraycopy(iv, 0, key_iv, aesKeyb.length, iv.length);
    ...
    byte[] RSAEncrypted = RSACipher.doFinal(key_iv);
    FileOutputStream out = new FileOutputStream("000000.00000.TA.840_Key");
    out.write(RSAEncrypted);
    out.close();
    ...
    

    Note: IVs do not have to be secret and therefore do not require encryption. Encryption is only required if script results are generated in Java code.

  • Another problem involves converting arbitrary binary data to strings. This often results in data corruption if the encoding is not appropriate, such as ASCII or UTF8. Therefore

    ...
    byte[] encoded = Files.readAllBytes(Paths.get(filePath));
    str = new String(encoded, StandardCharsets.US_ASCII);            Doesn't work: ASCII (7-bit) unsuitable for arbitrary bytes, *        
    ...
    byte[] AESEncrypted = AESCipher.doFinal(str.getBytes("UTF-8"));  Doesn't work: UTF-8 unsuitable for arbitrary bytes and additionally different from * 
    String encryptedStr = new String(AESEncrypted);                  Doesn't work: UTF-8 unsuitable for arbitrary bytes
    ...
    

    should be replaced with

    ...
    byte[] encoded = Files.readAllBytes(Paths.get(filePath));
    ...
    byte[] AESEncrypted = AESCipher.doFinal(encoded);
    FileOutputStream out = new FileOutputStream("PAYLOAD");
    out.write(AESEncrypted);
    out.close();
    ...
    

    A suitable encoding to store arbitrary data in a string is for example, Base64, but this is not required in this case because Base64 encoding is not used in the script.

  • Try these changes. If other issues occur, it is a good idea to test AES encryption, RSA encryption, and key_iv generation separately. This makes it much easier to isolate errors.

Related Problems and Solutions