How to encrypt and decrypt using AES CBC 256bit and PKCS5Padding in dart and also retrieve parameters

3,285

Here's a working example of encode in Java, decode in pointycastle.

Java

String plainText = "Hello World!";

KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(256);

SecretKey secret = generator.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);

byte[] encodedData = cipher.doFinal(Base64.getEncoder().encode(plainText.getBytes(StandardCharsets.UTF_8)));

Base64.Encoder encoder = Base64.getEncoder();
System.out.println(encoder.encodeToString(secret.getEncoded()));
System.out.println(encoder.encodeToString(cipher.getParameters().getEncoded())); // the DER encoded IV
System.out.println(encoder.encodeToString(encodedData));

Dart

import 'dart:convert';
import 'dart:typed_data';

import 'package:pointycastle/export.dart';

void main() {
  // the following 3 values were output from the above Java code
  var key = base64.decode('9JYmap3xB79oyBkY6ZIdJCXaOr/CurCK8XUsRZL9XXI=');
  var params = base64.decode('BBChkSMIq/v35PRRWAJGwtTr');
  var cipherText =
      base64.decode('Dh+lg2IMzcLC0toDRSoNMAQoR7MWKMLMPRi7KtdQdmw=');
  var iv = params.sublist(2); // strip the 4, 16 DER header

  var cipher = PaddedBlockCipherImpl(
    PKCS7Padding(),
    CBCBlockCipher(AESFastEngine()),
  );

  cipher.init(
    false /*decrypt*/,
    PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
      ParametersWithIV<KeyParameter>(KeyParameter(key), iv),
      null,
    ),
  );

  var plainishText = cipher.process(cipherText);

  print(utf8.decode(base64.decode(utf8.decode(plainishText))));
}

Encrypting in Dart

  var key = Uint8List(32); // the 256 bit key
  var plainText = 'Ciao Mondo';
  var random = Random.secure();
  var params = Uint8List(18)
    ..[0] = 4
    ..[1] = 16;
  for (int i = 2; i < 18; i++) {
    params[i] = random.nextInt(256);
  }
  var iv = params.sublist(2);

  var cipher = PaddedBlockCipherImpl(
    PKCS7Padding(),
    CBCBlockCipher(AESFastEngine()),
  )..init(
      true /*encrypt*/,
      PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
        ParametersWithIV<KeyParameter>(KeyParameter(key), iv),
        null,
      ),
    );

  var plainBytes = utf8.encode(base64.encode(utf8.encode(plainText)));
  var cipherText = cipher.process(plainBytes);

  // cipherText is the cipher text
  // params is the Java compatible params
Share:
3,285
Fern
Author by

Fern

Updated on December 16, 2022

Comments

  • Fern
    Fern over 1 year

    I have some java code which I am trying to replicate in Dart (class can be found at here)

        /**
         * Encrypt object with password
         * @param data Object to be encrypted
         * @param secret Password to use for encryption
         * @return Encrypted version of object
         */
        public static EncryptedBytes encrypt(String data, SecretKey secret) throws InvalidKeyException {
    
            try {
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.ENCRYPT_MODE, secret);
    
                // properly encode the complete ciphertext
                //logEncrypt(password, object);
    
                byte[] encodedData = cipher.doFinal(Base64.getEncoder().encode(data.getBytes(Charset.forName("UTF-8"))));
                byte[] params = cipher.getParameters().getEncoded();
                String paramAlgorithm = cipher.getParameters().getAlgorithm();
    
                return new EncryptedBytes(encodedData, params, paramAlgorithm);
            } catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | BadPaddingException | IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * Decrypt data with secret
         * @param encryptedBytes Object to be decrypted
         * @param secret Password to use for decryption
         * @return Decrypted version of object
         */
        public static String decrypt(EncryptedBytes encryptedBytes, @NonNull SecretKey secret) throws InvalidKeyException {
            try {
    
                // get parameter object for password-based encryption
                AlgorithmParameters algParams = AlgorithmParameters.getInstance(encryptedBytes.getParamAlgorithm());
    
                // initialize with parameter encoding from above
                algParams.init(encryptedBytes.getParams());
    
                Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                cipher.init(Cipher.DECRYPT_MODE, secret, algParams);
    
                return new String(Base64.getDecoder().decode(cipher.doFinal(encryptedBytes.getData())), Charset.forName("UTF-8"));
            } catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | IOException | InvalidAlgorithmParameterException e) {
                e.printStackTrace();
            }
            return null;
        }
    

    The EncryptedBytes class is simply just a data holder

    @RequiredArgsConstructor
    @Getter
    public class EncryptedBytes {
    
        private final byte[] data;
        private final byte[] params;
        private final String paramAlgorithm;
    
    }
    

    Right now in Dart I'm using PointyCastle and have gotten this close (not tested it though)

     static EncryptedBytes encrypt(String data, KeyParameter keyParameter) {
        final AESFastEngine aes = AESFastEngine()..init(false, keyParameter); // false=decrypt
    
        Uint8List encryptedData = aes.process(utf8.encode(data)); // Needs to convert to UTF8 then Base64 and finally be encrypted
        Uint8List params;
    
        String algorithm = aes.algorithmName;
    
        return EncryptedBytes(encryptedData, params, algorithm);
      }
    
      static String decrypt(EncryptedBytes data, KeyParameter keyParameter) {
        final AESFastEngine aes = AESFastEngine()..init(true, keyParameter); // true=encrypt
    
        String encryptedData = utf8.decode(aes.process(data.data)); // Needs to be decrypted, then decoded from Base64 and finally UTF8
    
        return encryptedData;
      }
    

    I'm not entirely sure what I can use to get an equivalent of the java code above in Dart. Base64 returns a String for encoding and requires a string for decoding while aes.process() requires and returns Uint8List

    • Richard Heap
      Richard Heap over 4 years
      Why does the java code base64 encode the bytes before encrypting them. That doesn't make much sense. The cipher needs an array of bytes, which is what you have before the base 64 step. After that step you end up with a string, which is not what the cipher needs.
    • Richard Heap
      Richard Heap over 4 years
      Where are you getting the IV in the Java code?
    • Fern
      Fern over 4 years
      @RichardHeap The parameters such as IV are provided when encrypting using EncryptedBytes and then retrieve those parameters when decrypting the object. Look decrypt and encrypt
    • Richard Heap
      Richard Heap over 4 years
      Do you happen to know how the Java implementation encodes the parameters here? getParameters().getEncoded() Is it in ASN.1?
    • Fern
      Fern over 4 years
      @RichardHeap I believe it is this I tried my best to find where the code goes. Hope this answers your question.
  • Fern
    Fern over 4 years
    I didn't really need the java code, just dart code but thanks anyway as it's useful. I just realized how redundant encoding and decoding the string in Base64 is when I could have just used UTF8 encoding/decoding by itself. Now all I need is encrypting in Dart, which the only thing I'm missing is getting the params and storing them in bytes the same way java does.
  • Fern
    Fern over 4 years
    Thank you for you super helpful answer ;)
  • Richard Heap
    Richard Heap over 4 years
    Please note the several minor changes to fix the padding problem. And, yes, I agree your base64 route isn't needed, but I guess you already have the Java end implemented.