在 Dart 中解密一个 Java AES 编码的字符串

Decrypt a Java AES encoded String in Dart

我需要在我的 Flutter 移动应用程序中解密 AES (PKCS#7) 编码的字符串。

该字符串是从 Java 应用程序生成并包含 AES 编码字符串的二维码中获取的。

Java编码:

import java.security.Security;
import java.nio.charset.StandardCharsets;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class MyClass {

     public static void main(String[] args) throws Exception {
         String toEncode = "firstname.lastname@mycompany.com;12";
         String encoded = pleaseEncodeMe(toEncode);
         System.out.println(encoded);
     }

     private static String pleaseEncodeMe(String plainText) throws Exception {
         Security.addProvider(new BouncyCastleProvider());
         final String encryptionAlgorithm = "AES/CBC/PKCS7PADDING";
         final String encryptionKey = "WHatAnAWEsoMeKey";
         final SecretKeySpec keySpecification = new SecretKeySpec(encryptionKey.getBytes(StandardCharsets.UTF_8), encryptionAlgorithm);
         final Cipher cipher = Cipher.getInstance(encryptionAlgorithm, "BC");
         cipher.init(Cipher.ENCRYPT_MODE, keySpecification);
         final byte[] encryptedBytes = cipher.doFinal(plainText.getBytes());
         return Base64.encodeBase64URLSafeString(encryptedBytes);
    }

}

输出:AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT

Dart解码:

void main() {
    print(decodeMeOrDie("AIRTEuNmSuQtYuysv93w3w83kJJ6sg7kaU7XzA8xrAjOp-lKYPp1brtDAPbhSJmT"));
}

String decodeMeOrDie(String encryptedString) {
    final key = Key.fromUtf8("WHatAnAWEsoMeKey");
    final iv = IV.fromLength(16);
    final encrypter = Encrypter(AES(key, mode: AESMode.cbc, padding: "PKCS7"));
    return encrypter.decrypt64(encryptedString, iv: iv);
}

输出:Y��=X�Rȑ�"Qme@mycompany.com;12

可以看到只有一部分字符串被解码了

  • 必须考虑两件事:

    1) 解密需要用到加密的IV。

    2) 出于安全原因,必须为每次加密随机生成一个新的 IV,以便使用相同的密钥 here 不会多次使用 IV。

    因此,IV必须从加密端传递到解密端。这不会自动发生,但必须实施。

  • 一种可能性是连接IV和密文的字节数组。通常 IV 放在密文之前,结果是 Base64 编码的(如果需要),例如在 Java 中:

    // Concatenate IV and ciphertext
    byte[] iv = ...
    byte[] ciphertext = ...
    byte[] ivAndCiphertext = new byte[iv.length + ciphertext.length];
    System.arraycopy(iv, 0, ivAndCiphertext, 0, iv.length);
    System.arraycopy(ciphertext, 0, ivAndCiphertext, iv.length, ciphertext.length);
    // If required: Base64-encoding
    

    此数据传输到解密端,解密端在Base64解码后将两部分分开。在 AES-CBC 的情况下,IV 的长度为 16 个字节,因此前 16 个字节表示 IV,其余为密文。 IV 不需要加密,因为它不是秘密的。

    特别针对您的情况,这意味着您必须在 Java 端连接 IV 和密文并对结果进行 Base64 编码。在 Dart 端,你必须先进行 Base64 解码,然后才能将 IV 和密文这两个部分分开并用于以下解密。

  • 有两种方法可以在加密前生成 IV: Cipher-instance as in your example or explicit generation e.g. via SecureRandom. Both alternatives are discussed here 隐式生成。如果 IV 是隐式生成的(通过 Cipher-实例),则此 IV 必须通过 Cipher-实例确定,因为稍后需要解密:

    // Determine IV from cipher for later decryption
    byte[] iv = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
    

    如果 IV 是显式确定的(例如使用 SecureRandom),则必须将其传递给 Cipher-实例,以便在 运行 加密中使用。这是使用 IvParameterSpec.

    完成的
    // Assign IV to cipher so that it is used for current encryption
    byte[] iv = ...
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
    cipher.init(Cipher.ENCRYPT_MODE, secretkeySpec, ivParameterSpec);
    
  • 硬编码密钥通常不是好的做法(也许出于测试目的除外)。但是,key generation/management 的主题不在本答案的范围内。关于这个主题已经有很多问题和答案。如果这些答案未涵盖您的问题,请 post 提出一个新问题。硬编码 IV 不会出现在上述架构中,只能用于测试目的。


如果它可以帮助某人,这是我在 dart 中最终得到的代码(它使用 encrypt 包):

/// Decode the specified QR code encrypted string
static String decodeQrCode(String encryptedString) {
  try {
    // pad the encrypted base64 string with '=' characters until length matches a multiple of 4
    final int toPad = encryptedString.length % 4;
    if (toPad != 0) {
      encryptedString = encryptedString.padRight(encryptedString.length + toPad, "=");
    }

    // get first 16 bytes which is the initialization vector
    final iv = encrypt.IV(Uint8List.fromList(base64Decode(encryptedString).getRange(0, 16).toList()));

    // get cipher bytes (without initialization vector)
    final encrypt.Encrypted encrypted = encrypt.Encrypted(Uint8List.fromList(
        base64Decode(encryptedString).getRange(16, base64Decode(encryptedString).length).toList()));

    // decrypt the string using the key and the initialization vector
    final key = encrypt.Key.fromUtf8(YOUR_KEY);
    final encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc, padding: "PKCS7"));
    return encrypter.decrypt(encrypted, iv: iv);
  } catch (e) {
    _log.severe("Error while decoding QR code : $e");
    return null;
  }
}