Decrypt a Java AES encoded String in Dart

Tags: , , , ,



I need to decrypt an AES (PKCS#7) encoded string in my Flutter mobile application.

The string is got from a QR Code, which has been generated from a Java application and contains the AES encoded String.

The Java encoding :

import java.security.Security;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class MyClass {

     public static void main(String[] args) throws Exception {
         String toEncode = "firstname.lastname@mycompany.com;12";
         String encoded = pleaseEncodeMe(toEncode);
         System.out.println(encoded);
     }

     private static String pleaseEncodeMe(String plainText) throws Exception {
         Security.addProvider(new BouncyCastleProvider());
         final String encryptionAlgorithm = "AES/CBC/PKCS7PADDING";
         final String encryptionKey = "WHatAnAWEsoMeKey";
         final SecretKeySpec keySpecification = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), encryptionAlgorithm);
         final Cipher cipher = Cipher.getInstance(encryptionAlgorithm, "BC");
         cipher.init(Cipher.ENCRYPT_MODE, keySpecification);
         final byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
         return Base64.encodeBase64URLSafeString(encryptedBytes);
    }

}

Output : AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT

The Dart decoding :

void main() {
    print(decodeMeOrDie("AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT"));
}

String decodeMeOrDie(String encryptedString) {
    final key = Key.fromUtf8("WHatAnAWEsoMeKey");
    final iv = IV.fromLength(16);
    final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: "PKCS7"));
    return encrypter.decrypt64(encryptedString, iv: iv);
}

Output : Y��=X�Rȑ�"Qme@mycompany.com;12

You can see that only a part of the string is decoded.

Answer

  • Two things must be taken into account:

    1) For decryption, the IV used for encryption is required.

    2) For security reasons, a new IV must be randomly generated for each encryption so that no IV is used more than once with the same key, here.

    Therfore, the IV must be passed from the encryption-side to the decryption-side. This doesn’t happen automatically, but has to be implemented.

  • One possibility is to concatenate the byte-arrays of IV and ciphertext. Usually the IV is placed before the ciphertext and the result is Base64-encoded (if required), e.g. in Java:

    // Concatenate IV and ciphertext
    byte[] iv = ...
    byte[] ciphertext = ...
    byte[] ivAndCiphertext = new byte[iv.length + ciphertext.length];
    System.arraycopy(iv, 0, ivAndCiphertext, 0, iv.length);
    System.arraycopy(ciphertext, 0, ivAndCiphertext, iv.length, ciphertext.length);
    // If required: Base64-encoding
    

    This data is transmitted to the decryption-side, which separates both parts after Base64-decoding. In the case of AES-CBC, the IV is 16 bytes long, so the first 16 bytes represent the IV and the rest the ciphertext. The IV doesn’t need to be encrypted because it isn’t secret.

    Specifically for your case this means that you have to concatenate IV and ciphertext on the Java-side and to Base64-encode the result. On the Dart-side you have to Base64-decode first and then both parts, IV and ciphertext, can be separated and used for the following decryption.

  • There are two ways to generate the IV before encryption: Implicit generation by the Cipher-instance as in your example or explicit generation e.g. via SecureRandom. Both alternatives are discussed here. If the IV is generated implicitly (via the Cipher-instance), then this IV must be determined via the Cipher-instance, since it is later required for decryption:

    // Determine IV from cipher for later decryption
    byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
    

    If the IV is determined explicitly (e.g. using SecureRandom), it must be passed to the Cipher-instance so that it will be used in the running encryption. This is done using an IvParameterSpec.

    // Assign IV to cipher so that it is used for current encryption
    byte[] iv = ...
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
    cipher.init(Cipher.ENCRYPT_MODE, secretkeySpec, ivParameterSpec);
    
  • A hard-coded key is in general not good practice (except for testing purposes perhaps). However, the topic of key generation/management is outside the scope of this answer. There are already a lot of questions and answers on this subject. If your question is not covered by these answers, please post a new question. A hard-coded IV doesn’t occur within the above architecture and should only be used for testing purposes.




Source: stackoverflow