简体   繁体   中英

Compatible AES encryption and decryption for Flutter and javascript

I am trying to write two functions in flutter and Javascript which I can use throughout my project to encrypt or decrypt data using AES when data is exchanged. For Flutter, I am using the pointycastle package based on instructions https://gist.github.com/proteye/e54eef1713e1fe9123d1eb04c0a5cf9b?signup=true

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

import "./convert_helper.dart";

// AES key size
const KEY_SIZE = 32; // 32 byte key for AES-256
const ITERATION_COUNT = 1000;

class AesHelper {
  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 SHA256Digest(), 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}) {
    Uint8List derivedKey = deriveKey(password);
    KeyParameter keyParam = new KeyParameter(derivedKey);
    BlockCipher aes = new AESFastEngine();

    var rnd = FortunaRandom();
    rnd.seed(keyParam);
    Uint8List iv = rnd.nextBytes(aes.blockSize);

    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);
    Uint8List cipherIvBytes = new Uint8List(cipherBytes.length + iv.length)
      ..setAll(0, iv)
      ..setAll(iv.length, cipherBytes);

    return base64.encode(cipherIvBytes);
  }

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

    Uint8List cipherIvBytes = base64.decode(ciphertext);
    Uint8List iv = new Uint8List(aes.blockSize)
      ..setRange(0, aes.blockSize, cipherIvBytes);

    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;
  }
}

and class flutter convert_helper.dart

import "dart:typed_data";
import 'dart:convert';
import 'package:convert/convert.dart' as convert;

Uint8List createUint8ListFromString(String s) {
  var ret = new Uint8List(s.length);
  for (var i = 0; i < s.length; i++) {
    ret[i] = s.codeUnitAt(i);
  }
  return ret;
}

Uint8List createUint8ListFromHexString(String hex) {
  var result = new Uint8List(hex.length ~/ 2);
  for (var i = 0; i < hex.length; i += 2) {
    var num = hex.substring(i, i + 2);
    var byte = int.parse(num, radix: 16);
    result[i ~/ 2] = byte;
  }
  return result;
}

Uint8List createUint8ListFromSequentialNumbers(int len) {
  var ret = new Uint8List(len);
  for (var i = 0; i < len; i++) {
    ret[i] = i;
  }
  return ret;
}

String formatBytesAsHexString(Uint8List bytes) {
  var result = new StringBuffer();
  for (var i = 0; i < bytes.lengthInBytes; i++) {
    var part = bytes[i];
    result.write('${part < 16 ? '0' : ''}${part.toRadixString(16)}');
  }
  return result.toString();
}

List<int> decodePEM(String pem) {
  var startsWith = [
    "-----BEGIN PUBLIC KEY-----",
    "-----BEGIN PRIVATE KEY-----",
    "-----BEGIN ENCRYPTED MESSAGE-----",
    "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
    "-----BEGIN PGP PRIVATE KEY BLOCK-----\r\nVersion: React-Native-OpenPGP.js 0.1\r\nComment: http://openpgpjs.org\r\n\r\n",
  ];
  var endsWith = [
    "-----END PUBLIC KEY-----",
    "-----END PRIVATE KEY-----",
    "-----END ENCRYPTED MESSAGE-----",
    "-----END PGP PUBLIC KEY BLOCK-----",
    "-----END PGP PRIVATE KEY BLOCK-----",
  ];
  bool isOpenPgp = pem.indexOf('BEGIN PGP') != -1;

  for (var s in startsWith) {
    if (pem.startsWith(s)) {
      pem = pem.substring(s.length);
    }
  }

  for (var s in endsWith) {
    if (pem.endsWith(s)) {
      pem = pem.substring(0, pem.length - s.length);
    }
  }

  if (isOpenPgp) {
    var index = pem.indexOf('\r\n');
    pem = pem.substring(0, index);
  }

  pem = pem.replaceAll('\n', '');
  pem = pem.replaceAll('\r', '');

  return base64.decode(pem);
}

List<int> decodeHex(String hex) {
  hex = hex
      .replaceAll(':', '')
      .replaceAll('\n', '')
      .replaceAll('\r', '')
      .replaceAll('\t', '');

  return convert.hex.decode(hex);
}

For the Javascript solution, I am using CryptoJS

var AESKey = "20190225165436_15230006321670000"
cc = CryptoJS.AES.encrypt( ("abcdef ha ha "), AESKey, { mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ).toString()
CryptoJS.AES.decrypt(cc, AESKey).toString(CryptoJS.enc.Utf8);  //return abcdef ha ha 

Both solutions work well within their own environment, however, the flutter or Javascript hashes can't be exchanged, they will not decrypt. My guess is that the character encoding has something to do with it, hence why the base64 sizes differ so much. Does anyone have an idea to get this working together? Thanks!

Does anyone have an idea to get this working together?

    import 'dart:convert';
    import 'dart:math';
    import 'dart:typed_data';
    import 'package:crypto/crypto.dart';
    import 'package:tuple/tuple.dart';
    import 'package:encrypt/encrypt.dart' as encrypt;

    String encryptAESCryptoJS(String plainText, String passphrase) {
    try {
        final salt = genRandomWithNonZero(8);
        var keyndIV = deriveKeyAndIV(passphrase, salt);
        final key = encrypt.Key(keyndIV.item1);
        final iv = encrypt.IV(keyndIV.item2);

        final encrypter = encrypt.Encrypter(
            encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
        final encrypted = encrypter.encrypt(plainText, iv: iv);
        Uint8List encryptedBytesWithSalt = Uint8List.fromList(
            createUint8ListFromString("Salted__") + salt + encrypted.bytes);
        return base64.encode(encryptedBytesWithSalt);
    } catch (error) {
        throw error;
    }
    }

    String decryptAESCryptoJS(String encrypted, String passphrase) {
    try {
        Uint8List encryptedBytesWithSalt = base64.decode(encrypted);

        Uint8List encryptedBytes =
            encryptedBytesWithSalt.sublist(16, encryptedBytesWithSalt.length);
        final salt = encryptedBytesWithSalt.sublist(8, 16);
        var keyndIV = deriveKeyAndIV(passphrase, salt);
        final key = encrypt.Key(keyndIV.item1);
        final iv = encrypt.IV(keyndIV.item2);

        final encrypter = encrypt.Encrypter(
            encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
        final decrypted =
            encrypter.decrypt64(base64.encode(encryptedBytes), iv: iv);
        return decrypted;
    } catch (error) {
        throw error;
    }
    }

    Tuple2<Uint8List, Uint8List> deriveKeyAndIV(String passphrase, Uint8List salt) {
    var password = createUint8ListFromString(passphrase);
    Uint8List concatenatedHashes = Uint8List(0);
    Uint8List currentHash = Uint8List(0);
    bool enoughBytesForKey = false;
    Uint8List preHash = Uint8List(0);

    while (!enoughBytesForKey) {
        int preHashLength = currentHash.length + password.length + salt.length;
        if (currentHash.length > 0)
        preHash = Uint8List.fromList(
            currentHash + password + salt);
        else
        preHash = Uint8List.fromList(
            password + salt);

        currentHash = md5.convert(preHash).bytes;
        concatenatedHashes = Uint8List.fromList(concatenatedHashes + currentHash);
        if (concatenatedHashes.length >= 48) enoughBytesForKey = true;
    }

    var keyBtyes = concatenatedHashes.sublist(0, 32);
    var ivBtyes = concatenatedHashes.sublist(32, 48);
    return new Tuple2(keyBtyes, ivBtyes);
    }

    Uint8List createUint8ListFromString(String s) {
    var ret = new Uint8List(s.length);
    for (var i = 0; i < s.length; i++) {
        ret[i] = s.codeUnitAt(i);
    }
    return ret;
    }

    Uint8List genRandomWithNonZero(int seedLength) {
    final random = Random.secure();
    const int randomMax = 245;
    final Uint8List uint8list = Uint8List(seedLength);
    for (int i=0; i < seedLength; i++) {
        uint8list[i] = random.nextInt(randomMax)+1;
    }
    return uint8list;
    }

Please refer below link for solution.

https://medium.com/@chingsuehok/cryptojs-aes-encryption-decryption-for-flutter-dart-7ca123bd7464

对于谁是寻找一个C#解决那些人(RijndaelManaged的&Rfc2898DeriveBytes)和颤振链接

The main problem is that the two key derivation algorithms are different. In your Dart/PC code you are using PBKDF2, but the KDF used by CryptoJS.AES.encrypt is OpenSSL'sEVP_BytesToKey

You could implement the equivalent of EVP_BytesToKey in Dart using PC. However it would probably be easier to change the JavaScript code to derive its key using PBKDF2, which is already supported by CryptoJS . That will give you a key and IV to be used like this :

var encrypted = CryptoJS.AES.encrypt("Message", key, { iv: iv });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM