fluttercryptographypointycastle

AES encryption using pointycastle


I'm new at the area of cryptography, and I'm trying to do a AES encryption and decryption functions in flutter, I'm using pointycastle (and I'm prefer not to change the package) and the problem is when I'm trying to decrypt

This is the code:

Future<String> aesEncrypt(String plainText, key, iv) async {
    final convertedText = utf8.encode(plainText);
    final keyBytes = base64Decode(key);
    final ivbytes = base64Decode(iv);

    final keyParams = KeyParameter(keyBytes);
    final params = ParametersWithIV(keyParams, ivbytes);

    final cipher = BlockCipher('AES/CBC')..init(true, params);

    final paddedData = _padData(convertedText);
    final encryptedBytes = cipher.process(paddedData);
    return base64Encode(encryptedBytes);
  }

  Future<String> aesDecrypt(String encryptedText, key, iv) async {
    final data = base64Decode(encryptedText);
    
    final keyBytes = base64Decode(key);
    final ivbytes = base64Decode(iv);

    final keyParams = KeyParameter(keyBytes);
    final params = ParametersWithIV(keyParams, ivbytes);

    final cipher = BlockCipher('AES/CBC')..init(false, params);

    final decryptedBytes = cipher.process(data);
    final unpaddedData = _unpadData(decryptedBytes);

    return utf8.decode(unpaddedData);
  }

  Uint8List _padData(Uint8List data) {
    final blockSize = 16;
    final padLength = blockSize - (data.length % blockSize);
    final paddedData = Uint8List(data.length + padLength)..setAll(0, data);
    for (int i = data.length; i < paddedData.length; i++) {
      paddedData[i] = padLength;
    }
    return paddedData;
  }

  Uint8List _unpadData(Uint8List data){
    final padLength = data[data.length - 1];
    return Uint8List.sublistView(data, 0, data.length - padLength);
  }

  String generateKey(){
    final random = math.Random.secure();

    //Generate random key
    final keyBytes = Uint8List(16);
    for(int i = 0; i < keyBytes.length; i++){
      keyBytes[i] = random.nextInt(256);
    }

    return base64Encode(keyBytes);
  }

  String generateIV(){
    final random = math.Random.secure();

    //Generate random key
    final ivBytes = Uint8List(16);
    for(int i = 0; i < ivBytes.length; i++){
      ivBytes[i] = random.nextInt(256);
    }

    return base64Encode(ivBytes);
  }

(leave the fact that the generate key and iv functions actually the same),

and the error is in _unpadData function at the line return Uint8List.sublistView(data, 0, data.length - padLength);

the error is: Exception has occurred. RangeError (RangeError (end): Invalid value: Not in inclusive range 0..16: -89)

Anyone have an idea why is this happening ?


Solution

  • AES/CBC/PKCS#7Padding is much easier to implement with PointyCastle (e.g. PKCS#7 Padding is supported and does not need to be implemented):

    import 'dart:typed_data';
    import 'package:pointycastle/export.dart';
    ...
    Uint8List crypt(bool isEncrypt, Uint8List data, Uint8List key, Uint8List iv) {
      final params = ParametersWithIV<KeyParameter>(KeyParameter(key), iv);
      final paddingParams = PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(params, null);
      final cipher = CBCBlockCipher(AESEngine());
      final paddingCipher = PaddedBlockCipherImpl(PKCS7Padding(), cipher)
        ..init(isEncrypt, paddingParams);
      return paddingCipher.process(data);
    }
    

    Example of use:

    import 'dart:convert';
    ...
    final key = utf8.encode('01234567890123456789012345678901'); // For the real world: Use a random byte sequence as key or derive the key from a password with a KDF
    final iv = utf8.encode('0987654321098765'); // For the real world: generate a random IV for each encryption and pass it with the ciphertext
    final plaintext = utf8.encode('The quick brown fox jumps over the lazy dog');
    final ciphertext = crypt(true, plaintext, key, iv); // encrypt
    final decrypted = crypt(false, ciphertext, key, iv); // decrypt
    print(base64Encode(ciphertext)); // viKpiZpMB+dBsLo6RuwKcVanKFyPHfLLxm4PlEWTAHxrPJ43KrA2y67sC6dBFsO4
    print(utf8.decode(decrypted)); // The quick brown fox jumps over the lazy dog
    

    Note that nowadays authenticated encyption should be used, e.g. GCM mode.

    In addition, to avoid key/IV reuse, a random IV should be generated for each encryption and passed along with the ciphertext to the decrypting side, typically concatenated (this can and is often implemented in the encryption method).
    On the decrypting side, the IV and ciphertext are both separated based on the known length of the IV (often implemented in the decryption method).