Solved: How can i encrypt a string in Dart same like in c# using RijndaelManaged

Question

Asked by Ajay A on January 24, 2023 (source).

This is my c# code for string encryption using RijndaelManaged, I'm not able to encrypt like same in flutter, i tried many packages. but no result. i need to encrypt a string in flutter and i need to decrypt in c#

public static string key = Environment.GetEnvironmentVariable("ENCR_KEY");
private const int Keysize = 256;
private const int DerivationIterations = 100;

public string Encrypt(string plainText)
{  
    var saltStringBytes = Generate256BitsOfRandomEntropy();
    var ivStringBytes = Generate256BitsOfRandomEntropy();
    var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
    using (var password = new Rfc2898DeriveBytes(key, saltStringBytes, DerivationIterations))
    {
        var keyBytes = password.GetBytes(Keysize / 8);
        using (var symmetricKey = new RijndaelManaged())
        {
            symmetricKey.BlockSize = 128;
            symmetricKey.Mode = CipherMode.CBC;
            //symmetricKey.Padding = PaddingMode.PKCS7;
            using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes))
            {
                using (var memoryStream = new MemoryStream())
                {
                    using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                    {
                        cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
                        cryptoStream.FlushFinalBlock();
                        var cipherTextBytes = saltStringBytes;
                        cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray();
                        cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray();
                        memoryStream.Close();
                        cryptoStream.Close();
                        return Convert.ToBase64String(cipherTextBytes);
                    }
                }
            }
        }
    }
}

private static byte[] Generate256BitsOfRandomEntropy()
{
    var randomBytes = new byte[16]; 
    using (var rngCsp = new RNGCryptoServiceProvider())
    {
        rngCsp.GetBytes(randomBytes);
    }
    return randomBytes;
}

public string Decrypt(string cipherText)
{
    string password = key;
    byte[] cipherBytes = Convert.FromBase64String(cipherText);
    using (Aes encryptor = Aes.Create())
    {
        var salt = cipherBytes.Take(16).ToArray();
        var iv = cipherBytes.Skip(16).Take(16).ToArray();
        var encrypted = cipherBytes.Skip(32).ToArray();
        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(password, salt, 100);
        encryptor.Key = pdb.GetBytes(32);
        encryptor.Padding = PaddingMode.PKCS7;
        encryptor.Mode = CipherMode.CBC;
        encryptor.IV = iv;
        using (MemoryStream ms = new MemoryStream(encrypted))
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Read))
            {
                using (var reader = new StreamReader(cs, Encoding.UTF8))
                {
                    return reader.ReadToEnd();
                }
            }
        }
    }
}

When i try encrypted cipher to decrypt in c#. I'm getting this error, "Padding is invalid and cannot be removed."

This is my Dart code

import 'dart:convert';
import 'package:pointycastle/block/aes_fast.dart';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';
import 'package:pointycastle/key_derivators/pbkdf2.dart';
import 'package:pointycastle/paddings/pkcs7.dart';
import 'package:pointycastle/pointycastle.dart';

const KEY_SIZE = 32; // 32 byte key for AES-256
const ITERATION_COUNT = 2;
const SALT = "XXXXXXXXXXXXXXXXX";
const INITIAL_VECTOR = "ZZZZZZZZZZZZZZZZ";
const PASS_PHRASE = "YYYYYYYYYYYYYYYYYYY";

Future<String> cryptString(String text) async {
  String encryptedString = "";

  final mStrPassPhrase = toUtf8(PASS_PHRASE);

  encryptedString = AesHelperMethod2.encrypt(
    mStrPassPhrase,
    toUtf8(text),
    mode: AesHelperMethod2.CBC_MODE,
  );

  return encryptedString;
}

Future<String> decryptString(String text) async {
  String decryptedString = "";

  final mStrPassPhrase = toUtf8(PASS_PHRASE);

  decryptedString = AesHelperMethod2.decrypt(mStrPassPhrase, toUtf8(text),
      mode: AesHelperMethod2.CBC_MODE);

  return decryptedString;
}

///MARK: AesHelper class
class AesHelperMethod2 {
  static const CBC_MODE = 'CBC';
  static const CFB_MODE = 'CFB';

  static Uint8List deriveKey(dynamic password,
      {String salt = '',
      int iterationCount = ITERATION_COUNT,
      int derivedKeyLength = KEY_SIZE}) {
    if (password == null || password.isEmpty) {
      throw new ArgumentError('password must not be empty');
    }

    if (password is String) {
      password = createUint8ListFromString(password);
    }

    Uint8List saltBytes = createUint8ListFromString(salt);
    Pbkdf2Parameters params =
        new Pbkdf2Parameters(saltBytes, iterationCount, derivedKeyLength);
    KeyDerivator keyDerivator =
        new PBKDF2KeyDerivator(new HMac(new SHA1Digest(), 64));
    keyDerivator.init(params);

    return keyDerivator.process(password);
  }

  static Uint8List pad(Uint8List src, int blockSize) {
    var pad = new PKCS7Padding();
    pad.init(null);

    int padLength = blockSize - (src.length % blockSize);
    var out = new Uint8List(src.length + padLength)..setAll(0, src);
    pad.addPadding(out, src.length);

    return out;
  }

  static Uint8List unpad(Uint8List src) {
    var pad = new PKCS7Padding();
    pad.init(null);

    int padLength = pad.padCount(src);
    int len = src.length - padLength;

    return new Uint8List(len)..setRange(0, len, src);
  }

  static String encrypt(String password, String plaintext,
      {String mode = CBC_MODE}) {
    String salt = toASCII(SALT);
    Uint8List derivedKey = deriveKey(password, salt: salt);
    KeyParameter keyParam = new KeyParameter(derivedKey);
    BlockCipher aes = new AESFastEngine();

    var ivStr = toASCII(INITIAL_VECTOR);
    Uint8List iv = createUint8ListFromString(ivStr);

    BlockCipher cipher;
    ParametersWithIV params = new ParametersWithIV(keyParam, iv);
    switch (mode) {
      case CBC_MODE:
        cipher = new CBCBlockCipher(aes);
        break;
      case CFB_MODE:
        cipher = new CFBBlockCipher(aes, aes.blockSize);
        break;
      default:
        throw new ArgumentError('incorrect value of the "mode" parameter');
        break;
    }
    cipher.init(true, params);

    Uint8List textBytes = createUint8ListFromString(plaintext);
    Uint8List paddedText = pad(textBytes, aes.blockSize);
    Uint8List cipherBytes = _processBlocks(cipher, paddedText);
    final enc = base64.encode(cipherBytes);
    print("enc : " "$enc");
    return base64.encode(cipherBytes);
  }

  static String decrypt(String password, String ciphertext,
      {String mode = CBC_MODE}) {
    String salt = toASCII(SALT);
    Uint8List derivedKey = deriveKey(password, salt: salt);
    KeyParameter keyParam = new KeyParameter(derivedKey);
    BlockCipher aes = new AESFastEngine();

    var ivStr = toASCII(INITIAL_VECTOR);
    Uint8List iv = createUint8ListFromString(ivStr);
    Uint8List cipherBytesFromEncode = base64.decode(ciphertext);

    Uint8List cipherIvBytes =
        new Uint8List(cipherBytesFromEncode.length + iv.length)
          ..setAll(0, iv)
          ..setAll(iv.length, cipherBytesFromEncode);

    BlockCipher cipher;

    ParametersWithIV params = new ParametersWithIV(keyParam, iv);
    switch (mode) {
      case CBC_MODE:
        cipher = new CBCBlockCipher(aes);
        break;
      case CFB_MODE:
        cipher = new CFBBlockCipher(aes, aes.blockSize);
        break;
      default:
        throw new ArgumentError('incorrect value of the "mode" parameter');
        break;
    }
    cipher.init(false, params);

    int cipherLen = cipherIvBytes.length - aes.blockSize;
    Uint8List cipherBytes = new Uint8List(cipherLen)
      ..setRange(0, cipherLen, cipherIvBytes, aes.blockSize);
    Uint8List paddedText = _processBlocks(cipher, cipherBytes);
    Uint8List textBytes = unpad(paddedText);

    return new String.fromCharCodes(textBytes);
  }

  static Uint8List _processBlocks(BlockCipher cipher, Uint8List inp) {
    var out = new Uint8List(inp.lengthInBytes);

    for (var offset = 0; offset < inp.lengthInBytes;) {
      var len = cipher.processBlock(inp, offset, out, offset);
      offset += len;
    }

    return out;
  }
}

///MARK: HELPERS
Uint8List createUint8ListFromString(String s) {
  Uint8List ret = Uint8List.fromList(s.codeUnits);

  return ret;
}

String toUtf8(value) {
  var encoded = utf8.encode(value);
  var decoded = utf8.decode(encoded);
  return decoded;
}

String toASCII(value) {
  var encoded = ascii.encode(value);
  var decoded = ascii.decode(encoded);
  return decoded;
}

Answer

Question answered by Topaco (source).

The C# encrypt() method does the following:

  • Generating a random 16 bytes salt and a random 16 bytes IV
  • Deriving a key with PBKDF2 using the following parameters
    • Key size 32 bytes
    • Digest: Sha-1
    • Iteration count: 100 (generally much too small for PBKDF2!)
  • Encrypting with AES in CBC mode and PKCS#7 padding
  • Concatenating salt, IV and ciphertext in that order and Base64 encoding

These functional building blocks must be replicated in the Dart code: For this you need a function that generates random values. So far, there is no such thing in the posted code. Also, the deriveKey() function for key derivation needs to be refactored. Other functionalities that could be encapsulated in functions are encryption with AES in CBC mode and PKCS#7 Padding as well as concatenation and encoding of the data.

  • Possible implementation to generate random values:
SecureRandom getSecureRandom() {
  List<int> seed = List<int>.generate(32, (_) => Random.secure().nextInt(256));
  return FortunaRandom()..seed(KeyParameter(Uint8List.fromList(seed)));
}
  • Refactoring of the deriveKey() method
Uint8List deriveKey(Uint8List salt, Uint8List passphrase){
  KeyDerivator derivator = KeyDerivator('SHA-1/HMAC/PBKDF2');
  Pbkdf2Parameters params = Pbkdf2Parameters(salt, 100, 256~/8);
  derivator.init(params);
  return derivator.process(passphrase);
}
  • Implementation of a method that encrypts with AES in CBC mode and PKCS#7 padding:
Uint8List encryptAesCbcPkcs7(Uint8List plaintext, Uint8List key, Uint8List iv){
  CBCBlockCipher cipher = CBCBlockCipher(AESEngine());
  ParametersWithIV<KeyParameter> params = ParametersWithIV<KeyParameter>(KeyParameter(key), iv);
  PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null> paddingParams = PaddedBlockCipherParameters<ParametersWithIV<KeyParameter>, Null>(params, null);
  PaddedBlockCipherImpl paddingCipher = PaddedBlockCipherImpl(PKCS7Padding(), cipher);
  paddingCipher.init(true, paddingParams);
  Uint8List ciphertext = paddingCipher.process(plaintext);
  return ciphertext;
}
  • And finally, a method for concatenating and encoding the data:
String concatAndEncode(Uint8List salt, Uint8List iv, Uint8List ciphertext){
  BytesBuilder saltIvCiphertext = BytesBuilder();
  saltIvCiphertext.add(salt);
  saltIvCiphertext.add(iv);
  saltIvCiphertext.add(ciphertext);
  String saltIvCiphertextB64 =  base64Encode(saltIvCiphertext.toBytes());
  return saltIvCiphertextB64;
}

Then these functional blocks only need to be wired:

import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:pointycastle/export.dart';

...

Uint8List plaintext = Uint8List.fromList(utf8.encode("The quick brown fox jumps over the lazy dog"));
Uint8List passphrase = Uint8List.fromList(utf8.encode("my passphrase"));

// Generate random 16 bytes salt and random 16 bytes IV
SecureRandom secureRandom = getSecureRandom();
Uint8List salt = secureRandom.nextBytes(16);
Uint8List iv = secureRandom.nextBytes(16);

// Derive 32 bytes key via PBKDF2
Uint8List key = deriveKey(salt, passphrase);

// Encrypt with AES-256/CBC/PKCS#7 padding
Uint8List ciphertext = encryptAesCbcPkcs7(plaintext, key, iv);

// Concat salt|nonce|ciphertext and Base64 encode
String saltIvCiphertextB64 =  concatAndEncode(salt, iv, ciphertext);

print(saltIvCiphertextB64); // e.g. 3igL9PVjgWpCTwYHP2GluZ/8lUaNblnGFEjZFDEiGvdnjoR/RkXIEtcPmgsnC4MmsfesGXo8Jls2vnCISoVAkzIZvadxbw5Dq1QddeMPnS0=

A ciphertext generated with this Dart code can be decrypted with the C# code.

AES DART DART-PUB ENCRYPTION FLUTTER
SHARE: