当我使用Dart加密纯文本,然后从Java代码中解密加密文本时,我收到以下错误:
javax.crypto.BadPaddingException: pad block corrupted
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher$BufferedGenericBlockCipher.doFinal(Unknown Source)
at org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(Unknown Source)
at javax.crypto.Cipher.doFinal(Cipher.java:2168)
at AesUtil.doFinal(AesUtil.java:75)
at AesUtil.decrypt(AesUtil.java:60)
at Main.main(Main.java:18)
相同的IV、salt和pasphase值使用Java端生成密钥,但生成的密钥不同,密码测试也不同。我使用相同的方法生成密钥。我不知道Dart代码中缺少了什么。
dependencies:
encrypt: ^5.0.1
hex: ^0.2.0
password_hash_plus: ^4.0.0
Dart代码为:
var random = Random.secure();
var values = List<int>.generate(16, (i) => random.nextInt(255));
// final salt = aes.IV.fromSecureRandom(16);
final salt = hex.encode(values);
final generator = PBKDF2(hashAlgorithm: sha1);
final key = aes.Key.fromBase64(generator.generateBase64Key("1234567891234567", salt, 1000, 16));
final iv = aes.IV.fromSecureRandom(16);
final encrypter =
aes.Encrypter(aes.AES(key, mode: aes.AESMode.cbc, padding: 'PKCS7'));
final encrypted = encrypter.encrypt(st.password!, iv: iv);
var str = '${iv.base16}::${salt}::${encrypted.base64}';
var bytes = utf8.encode(str);
var base64Str = base64.encode(bytes);
//final decrypt = encrypter.decrypt64("/vvAYMc3rgCvPvuSVU/qQw==", iv: iv);
print(
'------------------------------,\n encrypt ${(encrypted.base64)}-----------'
//'--\ndecrypted ${decrypt}-----------base64--------$base64Str-----'
'\nkey = ${key.base64} array--\niv = ${iv.base16}--salt= {${salt}');
Java代码是:
class Main {
public static void main(String[] args) {
AesUtil aesUtil = new AesUtil();
String encrypt = aesUtil.encrypt("b9266c74df614967d9acaa2878bff87c", "6ab7c799d6411f9d0c8e048ad526eeee", "1234567891234567", "Jitu@123456");
String a = aesUtil.decrypt("01e6a073a4255c92e704bd94d76d75c5", "98a21e07ed34afc523c5f5938c9202db", "1234567891234567", "MumTfpnzZh9bk94yiTuA+g==");
System.out.println("encrypt = " + encrypt + " \ndecrpty valaue----" + a);
}
}
Java中的加密代码:
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.springframework.stereotype.Component;
public class AesUtil {
private final int keySize;
private final int iterationCount;
private final Cipher cipher;
public AesUtil() {
this.keySize = 128;
this.iterationCount = 1000;
try {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
throw fail(e);
} catch (NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
public String encrypt(String salt, String iv, String passphrase, String plaintext) {
try {
SecretKey key = generateKey(salt, passphrase);
System.out.println("encryption key-------= " + base64(key.getEncoded()));
byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext.getBytes("ISO-8859-1"));
return base64(encrypted);
} catch (UnsupportedEncodingException e) {
throw fail(e);
}
}
public String decrypt(String salt, String iv, String passphrase, String ciphertext) {
try {
SecretKey key = generateKey(salt, passphrase);
System.out.println("decrypt key-------= " + base64(key.getEncoded()));
byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, base64(ciphertext));
return new String(decrypted, "ISO-8859-1");
} catch (UnsupportedEncodingException e) {
return null;
} catch (Exception e) {
return null;
}
}
private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) {
try {
IvParameterSpec IivParameterSpec = new IvParameterSpec(hex(iv));
System.out.println("----iv--= " + hex(IivParameterSpec.getIV()));
cipher.init(encryptMode, key, IivParameterSpec);
return cipher.doFinal(bytes);
} catch (InvalidKeyException
| InvalidAlgorithmParameterException
| IllegalBlockSizeException
| BadPaddingException e) {
e.printStackTrace();
return null;
}
}
private SecretKey generateKey(String salt, String passphrase) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
byte[] s = hex(salt);
System.out.println("salt-= " + hex(s));
KeySpec spec = new PBEKeySpec(passphrase.toCharArray(), s, iterationCount, keySize);
SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES");
return key;
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
return null;
}
}
public static String random(int length) {
byte[] salt = new byte[length];
new SecureRandom().nextBytes(salt);
return hex(salt);
}
public static String base64(byte[] bytes) {
return Base64.encodeBase64String(bytes);
}
public static byte[] base64(String str) {
return Base64.decodeBase64(str);
}
public static String hex(byte[] bytes) {
return Hex.encodeHexString(bytes);
}
public static byte[] hex(String str) {
try {
return Hex.decodeHex(str.toCharArray());
} catch (DecoderException e) {
throw new IllegalStateException(e);
}
}
private IllegalStateException fail(Exception e) {
return null;
}
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte) 0);
}
}
}
1条答案
按热度按时间ar7v8xwq1#
解密失败是因为两个代码使用了不同的salt,因此生成了不同的密钥。最终,这是由于 password_hash_plus Dart库的PBKDF 2实现设计不当所致。
在Java代码中,应用了一个 random salt,在本例中为0xb 9266 c74 df 614967 d9 acaa 2878 bff 87 c。在
main()
中,salt以十六进制编码传递到encrypt()
,在generateKey()
中以十六进制解码,生成的字节序列用于密钥派生。另一方面,password_hash_plus 库的
generateBase64Key()
方法要求salt为字符串,并 * 在内部 * 进行UTF-8编码,请参见here。因此,只能处理可进行UTF-8解码的salt。对于 random salt,通常情况并非如此,因为这些salt会被UTF-8解码破坏。当然,Dart代码中应用的salt的十六进制编码也不起作用,因为
generateBase64Key()
在内部不执行十六进制解码,而是执行UTF-8编码。由于salt通常是随机字节序列,因此 password_hash_plus 库的PBKDF 2实现的设计是不合适的。相反,需要将salt作为字节序列(
Uint8List
或List<int>
)传递的实现,例如PointyCastle的PBKDF 2实现:Dart代码返回十六进制编码的salt和IV,以及Base64编码的密文。如果此数据与这些编码一起传递到Java代码的
decrypt()
方法,则解密成功。请注意,迭代计数1000对于PBKDF 2来说通常太小。