我不知道为什么会出现错误.
线程“主”中的异常javax.crypto.BadPaddingException:给定的最终块未正确填充.如果在解密过程中使用了错误的密钥,则会出现此类问题.
我了解解密时使用了不正确的密钥会发生此错误.但是,如果查看下面的测试结果,您会发现C#和Java都是相同的(Key,IV,Salt是Base64编码的).
> C#测试结果
> Java测试结果
一样!(Key,IV,Salt)
但是会生成当前的BadpaddingException错误.可能是什么问题呢?
我正在附加我的源文件.
> C#(加密)
class AES {
private readonly static string keyStr = "This is Key";
private readonly static string vector = "This is Vector";
public static Rfc2898DeriveBytes MakeKey(string password){
byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(password);
byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(keyBytes, saltBytes, 65536);
return result;
}
public static Rfc2898DeriveBytes MakeVector(string vector){
byte[] vectorBytes = System.Text.Encoding.UTF8.GetBytes(vector);
byte[] saltBytes = SHA512.Create().ComputeHash(vectorBytes);
Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(vectorBytes, saltBytes, 65536);
return result;
}
public static void Encrypt(String inputFile, String outputFile) {
using (RijndaelManaged aes = new RijndaelManaged()){
//Create Key and Vector
Rfc2898DeriveBytes key = AES.MakeKey(AES.keyStr);
Rfc2898DeriveBytes vector = AES.MakeVector(AES.vector);
//AES256
aes.BlockSize = 128;
aes.KeySize = 256;
// It is equal in java
// Cipher _Cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
aes.Key = key.GetBytes(32); //256bit key
aes.IV = vector.GetBytes(16); //128bit block size
//processing Encrypt
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] encrypted;
using (MemoryStream msEncrypt = new MemoryStream()) {
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
byte[] inputBytes = File.ReadAllBytes(inputFile);
csEncrypt.Write(inputBytes, 0, inputBytes.Length);
}
encrypted = msEncrypt.ToArray();
}
string encodedString = Convert.ToBase64String(encrypted);
File.WriteAllText(outputFile, encodedString);
}
}
}
> Java(解密)
public class AES256File {
private static final String algorithm = "AES";
private static final String blockNPadding = algorithm+"/CBC/PKCS5Padding";
private static final String password = "This is Key";
private static final String IV = "This is Vector";
private static IvParameterSpec ivSpec;
private static Key keySpec;
public static void MakeKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] keyBytes = password.getBytes("UTF-8");
// C# : byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
byte[] saltBytes = digest.digest(keyBytes);
//256bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 65536, 256);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[32];
System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32);
SecretKeySpec secret = new SecretKeySpec(key, "AES");
setKeySpec(secret);
}
public static void MakeVector(String IV) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
MessageDigest digest = MessageDigest.getInstance("SHA-512");
byte[] vectorBytes = IV.getBytes("UTF-8");
byte[] saltBytes = digest.digest(vectorBytes);
// 128bit
PBEKeySpec pbeKeySpec = new PBEKeySpec(IV.toCharArray(), saltBytes, 65536, 128);
Key secretIV = factory.generateSecret(pbeKeySpec);
byte[] iv = new byte[16];
System.arraycopy(secretIV.getEncoded(), 0, iv, 0, 16);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
setIvSpec(ivSpec);
}
public void decrypt(File source, File dest) throws Exception {
Cipher c = Cipher.getInstance(blockNPadding);
c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
fileProcessing(source, dest, c);
}
public void fileProcessing(File source, File dest, Cipher c) throws Exception{
InputStream input = null;
OutputStream output = null;
try{
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
byte[] deryptedBytes = c.doFinal(buffer); // -----------------------> Error!! Showing!
byte[] decodedBytes = Base64.getDecoder().decode(deryptedBytes);
String decodeString = new String(decodedBytes, "UTF-8");
decodedBytes = decodeString.getBytes(StandardCharsets.UTF_8);
output.write(decodedBytes);
}finally{
if(output != null){
try{output.close();}catch(IOException e){}
}
if(input != null){
try{input.close();}catch(IOException e){}
}
}
}
我已验证如下.
> C#中的验证密钥和IV
//Key Verification
var salt = Convert.ToBase64String(saltBytes);
Console.Write("Salt Result : ");
Console.WriteLine(salt);
var result_test = Convert.ToBase64String(result.GetBytes(32));
Console.Write("Key Test Result: ");
Console.WriteLine(result_test);
//IV Verification (Salt is Using same code)
var result_test = Convert.ToBase64String(result.GetBytes(16));
Console.Write("IV Test Result: ");
Console.WriteLine(result_test);
> Java验证密钥和IV
//Key Verification
/* print Salt */
String base64 = Base64.getEncoder().encodeToString(saltBytes);
System.out.println("Salt Result : " + base64);
/* print Key */
String result_test = Base64.getEncoder().encodeToString(key);
System.out.println("Key Test Result : " + result_test);
/* print generated Key */
System.out.println("Secret Key Result : " + Base64.getEncoder().encodeToString(secret.getEncoded()));
//IV Verification (Salt is Using same code)
/* print IV */
String result_test = Base64.getEncoder().encodeToString(iv);
System.out.println("IV Test Result : " + result_test);
/* print generated IV */
System.out.println("IV Result : " + Base64.getEncoder().encodeToString(ivSpec.getIV()));
更新
c#.netframework 4.5 / Java8修改了@Topaco所说的内容,并确认它运行良好.
我想对@Topaco和@ Gusto2表示非常感谢,我将对安全性已修改的部分进行更改,就像@ Gusto2所说的一样!
解决方法:
1)在C#加密方法中,首先对纯文本进行加密,然后对Base64进行编码.因此,在解密过程中,必须先对数据进行Base64解码,然后再进行解密.当前,这是以错误的顺序处理的,即数据首先被解密然后被解码.因此,在Java fileProcessing-method中替换
while((read = input.read(buffer)) != -1){
output.write(c.update(buffer, 0, read));
}
与
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
2)不必将缓冲区(或bufferDecoded)传递给doFinal方法,因为这已在update-method中完成.从而,
byte[] deryptedBytes = c.doFinal(buffer);
必须替换为
output.write(c.doFinal());
3)由于已经在1)的try块中完成了Base64解码,因此必须删除doFinal语句之后的所有行.总体而言,这导致
try {
input = new BufferedInputStream(new FileInputStream(source));
output = new BufferedOutputStream(new FileOutputStream(dest));
byte[] buffer = new byte[input.available()];
int read = -1;
while((read = input.read(buffer)) != -1) {
byte[] bufferEncoded = buffer;
if (read != buffer.length) {
bufferEncoded = Arrays.copyOf(buffer, read);
}
byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
output.write(c.update(bufferDecoded));
}
output.write(c.doFinal());
}
4)缓冲区的大小必须为4的倍数,以确保正确的Base64解码.因此,更换更可靠
byte[] buffer = new byte[input.available()];
与
byte[] buffer = new byte[4 * (input.available() / 4)];
只要以一个块读取数据(这是不能保证的,例如参见https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html#available()),就没有问题.但是,如果以多个块读取数据,则读取4个字节的倍数很重要,否则Base64解码将失败.通过使用不是4的倍数的缓冲区大小,可以很容易地证明这一点.如果针对较大的文件明确定义了缓冲区大小,则也必须考虑这一点.