簡體   English   中英

我如何使用 RijndaelManaged 加密 Dart 中的字符串,就像在 c# 中一樣

[英]How can i encrypt a string in Dart same like in c# using RijndaelManaged

這是我使用 RijndaelManaged 進行字符串加密的 c# 代碼,我無法像 flutter 中那樣進行加密,我嘗試了很多包。 但沒有結果。 我需要在 flutter 中加密一個字符串,我需要在 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();
                }
            }
        }
    }
}

當我嘗試使用加密密碼在 c# 中解密時。我收到此錯誤消息“填充無效且無法刪除。”

這是我的 Dart 代碼

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

C# encrypt()方法執行以下操作:

  • 生成一個隨機的 16 字節鹽和一個隨機的 16 字節 IV
  • 使用以下參數通過 PBKDF2 派生密鑰
    • 密鑰大小 32 字節
    • 摘要:Sha-1
    • 迭代次數:100(通常對於 PBKDF2 來說太小了!)
  • 在 CBC 模式和 PKCS#7 填充中使用 AES 加密
  • 按順序連接鹽、IV 和密文以及 Base64 編碼

必須在 Dart 代碼中復制這些功能構建塊:為此,您需要一個生成隨機值的 function。 到目前為止,發布的代碼中沒有這樣的東西。 另外,需要重構用於密鑰推導的deriveKey() function。 可以封裝在函數中的其他功能包括在 CBC 模式下使用 AES 加密和 PKCS#7 填充以及數據的連接和編碼。

  • 生成隨機值的可能實現:
SecureRandom getSecureRandom() {
  List<int> seed = List<int>.generate(32, (_) => Random.secure().nextInt(256));
  return FortunaRandom()..seed(KeyParameter(Uint8List.fromList(seed)));
}
  • deriveKey()方法的重構
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);
}
  • 在 CBC 模式和 PKCS#7 填充中使用 AES 加密的方法的實現:
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;
}
  • 最后,一種連接和編碼數據的方法:
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;
}

那么這些功能塊只需要連線:

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=

用這個 Dart 代碼生成的密文可以用 C# 代碼解密。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM