首页 > 解决方案 > 兼容 Flutter 和 javascript 的 AES 加解密

问题描述

我正在尝试用颤振和 Javascript 编写两个函数,我可以在整个项目中使用它们在交换数据时使用 AES 加密或解密数据。对于 Flutter,我正在使用基于说明 https://gist.github.com/proteye/e54eef1713e1fe9123d1eb04c0a5cf9b?signup=true的 pointycastle 包

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

和类颤振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);
}

对于 Javascript 解决方案,我使用 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 

两种解决方案都可以在自己的环境中运行良好,但是,flutter 或 Javascript 哈希无法交换,它们不会解密。我的猜测是字符编码与它有关,因此为什么 base64 大小差异如此之大。有没有人有想法让它一起工作?谢谢!

有没有人有想法让它一起工作?

标签: dartfluttercryptojspointycastle

解决方案


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

请参阅以下链接以获取解决方案。

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


推荐阅读