flutterencryptiondartaespointycastle

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


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


Solution

  • 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