首选项 & javax.crypto.BadPaddingException:给定的最终块未正确填充
Preferences & javax.crypto.BadPaddingException: Given final block not properly padded
我已经盯着这段代码看了好几个小时了,我一定漏掉了一些愚蠢的东西。因此,非常感谢有人在这里提供帮助。
下面是示例代码,我能得到的最简单的代码来重现这个问题。该代码在第一次运行时运行良好,但在为 IV 创建首选项之后,它就失败了。
我的真实代码在首选项中存储的比这个多得多,我在故障排除中对其进行了很多简化,使其更容易隔离。
我的目标是创建一个 class,它允许我存储使用 AES 128 加密的首选项以及从用户提供的密码派生的密钥。例如安全偏好。我在故障排除中删除了所有用户提供的密码内容。
package com.test;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidParameterSpecException;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import org.apache.commons.codec.binary.Base64;
public class Settings {
private Preferences prefs = null;
private byte[] iv = null;
private SecretKey secret = null;
Cipher cipher = null;
public static void main(String[] args){
Settings t = new Settings();
String encText = t.encryptText("HELLO");//Encrypt a value
String output = t.decryptText(encText);//Decrypt the value
System.out.println(output); //Display the decrypted value.
}
public Settings(){
try {
String parentClass = new Exception().getStackTrace()[1].getClassName();//Really only controls where the prefs go, shouldn't matter.
this.prefs = Preferences.userNodeForPackage(Class.forName(parentClass));
Random r = new SecureRandom();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128 bit key
this.secret = keyGen.generateKey();
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (Exception ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
}
private String encryptText(String plainText){
try {
cipher.init(Cipher.ENCRYPT_MODE, this.secret);
AlgorithmParameters params = cipher.getParameters();
this.iv = prefs.getByteArray("IV", null);
if(this.iv == null){
this.iv = params.getParameterSpec(IvParameterSpec.class).getIV();
prefs.putByteArray("IV", this.iv);
}
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
String ret = new String(Base64.encodeBase64(ciphertext));
return ret;
} catch (InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return "";
}
private String decryptText(String cipherText){
try {
this.iv = prefs.getByteArray("IV", null);
byte[] cText = Base64.decodeBase64(cipherText);
cipher.init(Cipher.DECRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
String ret = new String(cipher.doFinal(cText), "UTF-8");
return ret;
} catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException | InvalidAlgorithmParameterException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return "";
}
}
仅在 2 次以上运行时收到的堆栈跟踪:
Feb 07, 2015 9:02:46 PM com.test.Settings decryptText
SEVERE: null
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at com.test.Settings.decryptText(Settings.java:77)
at com.test.Settings.main(Settings.java:34)
------------ 编辑正确答案------------
正如 GregS 指出的那样,当存在首选项时,我没有将 IV 加载到加密例程中,因此存在不匹配。以下是问题修复后更新的加密函数。
private String encryptText(String plainText){
try {
this.iv = prefs.getByteArray("IV", null);
if(this.iv == null) { //If not set, set the IV
cipher.init(Cipher.ENCRYPT_MODE, this.secret);
AlgorithmParameters params = cipher.getParameters();
this.iv = params.getParameterSpec(IvParameterSpec.class).getIV();
prefs.putByteArray("IV", this.iv);
} else {
cipher.init(Cipher.ENCRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
}
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
String ret = new String(Base64.encodeBase64(ciphertext));
return ret;
} catch (InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (InvalidAlgorithmParameterException ex) {
Logger.getLogger(Settings.class.getName()).log(Level.SEVERE, null, ex);
}
return "";
}
正如 GregS 指出的那样,当存在首选项时,我没有将 IV 加载到加密例程中,结果出现了不匹配。以下是问题修复后更新的加密函数。
private String encryptText(String plainText){
try {
this.iv = prefs.getByteArray("IV", null);
if(this.iv == null) { //If not set, set the IV
cipher.init(Cipher.ENCRYPT_MODE, this.secret);
AlgorithmParameters params = cipher.getParameters();
this.iv = params.getParameterSpec(IvParameterSpec.class).getIV();
prefs.putByteArray("IV", this.iv);
} else {
cipher.init(Cipher.ENCRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
}
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
String ret = new String(Base64.encodeBase64(ciphertext));
return ret;
} catch (InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (InvalidAlgorithmParameterException ex) {
Logger.getLogger(Settings.class.getName()).log(Level.SEVERE, null, ex);
}
return "";
}
我已经盯着这段代码看了好几个小时了,我一定漏掉了一些愚蠢的东西。因此,非常感谢有人在这里提供帮助。
下面是示例代码,我能得到的最简单的代码来重现这个问题。该代码在第一次运行时运行良好,但在为 IV 创建首选项之后,它就失败了。
我的真实代码在首选项中存储的比这个多得多,我在故障排除中对其进行了很多简化,使其更容易隔离。
我的目标是创建一个 class,它允许我存储使用 AES 128 加密的首选项以及从用户提供的密码派生的密钥。例如安全偏好。我在故障排除中删除了所有用户提供的密码内容。
package com.test;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidParameterSpecException;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import org.apache.commons.codec.binary.Base64;
public class Settings {
private Preferences prefs = null;
private byte[] iv = null;
private SecretKey secret = null;
Cipher cipher = null;
public static void main(String[] args){
Settings t = new Settings();
String encText = t.encryptText("HELLO");//Encrypt a value
String output = t.decryptText(encText);//Decrypt the value
System.out.println(output); //Display the decrypted value.
}
public Settings(){
try {
String parentClass = new Exception().getStackTrace()[1].getClassName();//Really only controls where the prefs go, shouldn't matter.
this.prefs = Preferences.userNodeForPackage(Class.forName(parentClass));
Random r = new SecureRandom();
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128); // 128 bit key
this.secret = keyGen.generateKey();
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
} catch (NoSuchAlgorithmException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (Exception ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
}
private String encryptText(String plainText){
try {
cipher.init(Cipher.ENCRYPT_MODE, this.secret);
AlgorithmParameters params = cipher.getParameters();
this.iv = prefs.getByteArray("IV", null);
if(this.iv == null){
this.iv = params.getParameterSpec(IvParameterSpec.class).getIV();
prefs.putByteArray("IV", this.iv);
}
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
String ret = new String(Base64.encodeBase64(ciphertext));
return ret;
} catch (InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return "";
}
private String decryptText(String cipherText){
try {
this.iv = prefs.getByteArray("IV", null);
byte[] cText = Base64.decodeBase64(cipherText);
cipher.init(Cipher.DECRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
String ret = new String(cipher.doFinal(cText), "UTF-8");
return ret;
} catch (IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException | InvalidAlgorithmParameterException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
}
return "";
}
}
仅在 2 次以上运行时收到的堆栈跟踪:
Feb 07, 2015 9:02:46 PM com.test.Settings decryptText
SEVERE: null
javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087)
at com.test.Settings.decryptText(Settings.java:77)
at com.test.Settings.main(Settings.java:34)
------------ 编辑正确答案------------ 正如 GregS 指出的那样,当存在首选项时,我没有将 IV 加载到加密例程中,因此存在不匹配。以下是问题修复后更新的加密函数。
private String encryptText(String plainText){
try {
this.iv = prefs.getByteArray("IV", null);
if(this.iv == null) { //If not set, set the IV
cipher.init(Cipher.ENCRYPT_MODE, this.secret);
AlgorithmParameters params = cipher.getParameters();
this.iv = params.getParameterSpec(IvParameterSpec.class).getIV();
prefs.putByteArray("IV", this.iv);
} else {
cipher.init(Cipher.ENCRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
}
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
String ret = new String(Base64.encodeBase64(ciphertext));
return ret;
} catch (InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (InvalidAlgorithmParameterException ex) {
Logger.getLogger(Settings.class.getName()).log(Level.SEVERE, null, ex);
}
return "";
}
正如 GregS 指出的那样,当存在首选项时,我没有将 IV 加载到加密例程中,结果出现了不匹配。以下是问题修复后更新的加密函数。
private String encryptText(String plainText){
try {
this.iv = prefs.getByteArray("IV", null);
if(this.iv == null) { //If not set, set the IV
cipher.init(Cipher.ENCRYPT_MODE, this.secret);
AlgorithmParameters params = cipher.getParameters();
this.iv = params.getParameterSpec(IvParameterSpec.class).getIV();
prefs.putByteArray("IV", this.iv);
} else {
cipher.init(Cipher.ENCRYPT_MODE, this.secret, new IvParameterSpec(this.iv));
}
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
String ret = new String(Base64.encodeBase64(ciphertext));
return ret;
} catch (InvalidParameterSpecException | IllegalBlockSizeException | BadPaddingException | UnsupportedEncodingException | InvalidKeyException ex) {
Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, null, ex);
} catch (InvalidAlgorithmParameterException ex) {
Logger.getLogger(Settings.class.getName()).log(Level.SEVERE, null, ex);
}
return "";
}