如何在 dart 中使用 AES CBC 256bit 和 PKCS5Padding 加密和解密并检索参数

Posted

技术标签:

【中文标题】如何在 dart 中使用 AES CBC 256bit 和 PKCS5Padding 加密和解密并检索参数【英文标题】:How to encrypt and decrypt using AES CBC 256bit and PKCS5Padding in dart and also retrieve parameters 【发布时间】:2020-04-18 19:03:03 【问题描述】:

我有一些我想在 Dart 中复制的 java 代码(类可以在 here 找到)

    /**
     * Encrypt object with password
     * @param data Object to be encrypted
     * @param secret Password to use for encryption
     * @return Encrypted version of object
     */
    public static EncryptedBytes encrypt(String data, SecretKey secret) throws InvalidKeyException 

        try 
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, secret);

            // properly encode the complete ciphertext
            //logEncrypt(password, object);

            byte[] encodedData = cipher.doFinal(Base64.getEncoder().encode(data.getBytes(Charset.forName("UTF-8"))));
            byte[] params = cipher.getParameters().getEncoded();
            String paramAlgorithm = cipher.getParameters().getAlgorithm();

            return new EncryptedBytes(encodedData, params, paramAlgorithm);
         catch (NoSuchAlgorithmException | IllegalBlockSizeException | NoSuchPaddingException | BadPaddingException | IOException e) 
            e.printStackTrace();
        
        return null;
    

    /**
     * Decrypt data with secret
     * @param encryptedBytes Object to be decrypted
     * @param secret Password to use for decryption
     * @return Decrypted version of object
     */
    public static String decrypt(EncryptedBytes encryptedBytes, @NonNull SecretKey secret) throws InvalidKeyException 
        try 

            // get parameter object for password-based encryption
            AlgorithmParameters algParams = AlgorithmParameters.getInstance(encryptedBytes.getParamAlgorithm());

            // initialize with parameter encoding from above
            algParams.init(encryptedBytes.getParams());

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, secret, algParams);

            return new String(Base64.getDecoder().decode(cipher.doFinal(encryptedBytes.getData())), Charset.forName("UTF-8"));
         catch (NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | IOException | InvalidAlgorithmParameterException e) 
            e.printStackTrace();
        
        return null;
    

EncryptedBytes 类只是一个数据持有者

@RequiredArgsConstructor
@Getter
public class EncryptedBytes 

    private final byte[] data;
    private final byte[] params;
    private final String paramAlgorithm;


我现在在 Dart 中使用的是 PointyCastle 并且已经接近了(虽然没有测试过)

 static EncryptedBytes encrypt(String data, KeyParameter keyParameter) 
    final AESFastEngine aes = AESFastEngine()..init(false, keyParameter); // false=decrypt

    Uint8List encryptedData = aes.process(utf8.encode(data)); // Needs to convert to UTF8 then Base64 and finally be encrypted
    Uint8List params;

    String algorithm = aes.algorithmName;

    return EncryptedBytes(encryptedData, params, algorithm);
  

  static String decrypt(EncryptedBytes data, KeyParameter keyParameter) 
    final AESFastEngine aes = AESFastEngine()..init(true, keyParameter); // true=encrypt

    String encryptedData = utf8.decode(aes.process(data.data)); // Needs to be decrypted, then decoded from Base64 and finally UTF8

    return encryptedData;
  

我不完全确定在 Dart 中我可以使用什么来获得与上面的 java 代码等效的代码。 Base64返回一个字符串进行编码,需要一个字符串进行解码,而aes.process()需要并返回Uint8List

【问题讨论】:

为什么java代码base64在加密之前对字节进行编码。这没有多大意义。密码需要一个字节数组,这是您在 base 64 步骤之前所拥有的。在这一步之后,你会得到一个字符串,这不是密码所需要的。 您从哪里获得 Java 代码中的 IV? @RichardHeap 使用 EncryptedBytes 加密时提供 IV 等参数,然后在解密对象时检索这些参数。看decrypt 和encrypt 你碰巧知道Java实现是如何对这里的参数进行编码的吗? getParameters().getEncoded() 是否在 ASN.1 中? @RichardHeap 我相信是this 我尽力找到代码的去向。希望这能回答您的问题。 【参考方案1】:

这是一个在 Java 中编码,在 pointycastle 中解码的工作示例。

Java

String plainText = "Hello World!";

KeyGenerator generator = KeyGenerator.getInstance("AES");
generator.init(256);

SecretKey secret = generator.generateKey();
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);

byte[] encodedData = cipher.doFinal(Base64.getEncoder().encode(plainText.getBytes(StandardCharsets.UTF_8)));

Base64.Encoder encoder = Base64.getEncoder();
System.out.println(encoder.encodeToString(secret.getEncoded()));
System.out.println(encoder.encodeToString(cipher.getParameters().getEncoded())); // the DER encoded IV
System.out.println(encoder.encodeToString(encodedData));

飞镖

import 'dart:convert';
import 'dart:typed_data';

import 'package:pointycastle/export.dart';

void main() 
  // the following 3 values were output from the above Java code
  var key = base64.decode('9JYmap3xB79oyBkY6ZIdJCXaOr/CurCK8XUsRZL9XXI=');
  var params = base64.decode('BBChkSMIq/v35PRRWAJGwtTr');
  var cipherText =
      base64.decode('Dh+lg2IMzcLC0toDRSoNMAQoR7MWKMLMPRi7KtdQdmw=');
  var iv = params.sublist(2); // strip the 4, 16 DER header

  var cipher = PaddedBlockCipherImpl(
    PKCS7Padding(),
    CBCBlockCipher(AESFastEngine()),
  );

  cipher.init(
    false /*decrypt*/,
    PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
      ParametersWithIV<KeyParameter>(KeyParameter(key), iv),
      null,
    ),
  );

  var plainishText = cipher.process(cipherText);

  print(utf8.decode(base64.decode(utf8.decode(plainishText))));

Dart 中的加密

  var key = Uint8List(32); // the 256 bit key
  var plainText = 'Ciao Mondo';
  var random = Random.secure();
  var params = Uint8List(18)
    ..[0] = 4
    ..[1] = 16;
  for (int i = 2; i < 18; i++) 
    params[i] = random.nextInt(256);
  
  var iv = params.sublist(2);

  var cipher = PaddedBlockCipherImpl(
    PKCS7Padding(),
    CBCBlockCipher(AESFastEngine()),
  )..init(
      true /*encrypt*/,
      PaddedBlockCipherParameters<CipherParameters, CipherParameters>(
        ParametersWithIV<KeyParameter>(KeyParameter(key), iv),
        null,
      ),
    );

  var plainBytes = utf8.encode(base64.encode(utf8.encode(plainText)));
  var cipherText = cipher.process(plainBytes);

  // cipherText is the cipher text
  // params is the Java compatible params

【讨论】:

我真的不需要 java 代码,只需要 dart 代码,但无论如何感谢它,因为它很有用。我刚刚意识到在 Base64 中对字符串进行冗余编码和解码时,我可以自己使用 UTF8 编码/解码。现在我只需要在 Dart 中进行加密,我唯一缺少的就是获取参数并将它们以与 java 相同的方式存储在字节中。 谢谢你的超级有用的回答;) 请注意修复填充问题的几个小改动。而且,是的,我同意不需要您的 base64 路由,但我想您已经实现了 Java 端。

以上是关于如何在 dart 中使用 AES CBC 256bit 和 PKCS5Padding 加密和解密并检索参数的主要内容,如果未能解决你的问题,请参考以下文章

无法使用来自 AES-256-CBC 的 pgcrypto 解密,但 AES-128-CBC 可以

如何使用java解码用openssl aes-128-cbc编码的字符串?

AES (aes-cbc-128, aes-cbc-192, aes-cbc-256) 使用 openssl C 加密/解密

如何安全地为 AES CBC 加密生成 IV?

如何正确使用来自 mbedtls 的 aes cbc api

如何让 Ruby AES-256-CBC 和 PHP MCRYPT_RIJNDAEL_128 一起玩得很好