在 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;
}
}
我需要在我的 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;
}
}