Android加密中的BadPaddingException

4

我正在开发一个Android应用程序,想在将字符串发送到数据库之前对其进行加密,目前加密已经成功。但是当我尝试解密字符串时,出现了"BadPaddingException"异常,我不知道问题出在哪里。以下是代码:

public final static String HEX = "36A52C8FB7DF9A3F";

public static String encrypt(String seed, String cleartext) throws Exception 
{
    byte[] rawKey = getRawKey(seed.getBytes());
    byte[] result = encrypt(rawKey, cleartext.getBytes());
    return toHex(result);
}

public static String decrypt(String seed, String encrypted) throws Exception 
{
    byte[] rawKey = getRawKey(seed.getBytes());
    byte[] enc = toByte(encrypted);
    byte[] result = decrypt(rawKey, enc);
    return new String(result);
}

public static String toHex(String txt) {
    return toHex(txt.getBytes());
}

public static String fromHex(String hex) {
    return new String(toByte(hex));
}

public static byte[] toByte(String hexString) {
    int len = hexString.length()/2;
    byte[] result = new byte[len];
    for (int i = 0; i < len; i++)
        result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
    return result;
}

public static String toHex(byte[] buf) {
    if (buf == null)
        return "";
    StringBuffer result = new StringBuffer(2*buf.length);
    for (int i = 0; i < buf.length; i++) {
        appendHex(result, buf[i]);
    }
    return result.toString();
}

private static byte[] getRawKey(byte[] seed) throws Exception {
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
    sr.setSeed(seed);
    kgen.init(128, sr); // 192 and 256 bits may not be available
    SecretKey skey = kgen.generateKey();
    byte[] raw = skey.getEncoded();
    return raw;
}

private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
    byte[] encrypted = cipher.doFinal(clear);
    return encrypted;
}

private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
    SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.DECRYPT_MODE, skeySpec);
    byte[] decrypted = cipher.doFinal(encrypted);
    return decrypted;
}

private static void appendHex(StringBuffer sb, byte b) {
    sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}

我使用以下代码进行加密和解密:

String encrypted = encrypt(HEX, "some text");
String decrypted = decrypt(HEX, encrypted);

请问有人可以帮我吗?

非常感谢!!

编辑:问题还没有解决,但是我有更多的信息。首先,我在一个Java项目中进行加密,然后在一个Android项目中进行解密。我尝试在同一个Java项目中进行解密,没有任何问题,但是如果我在Android中尝试解密,它就不起作用。问题出现在“getRawKey”方法中(请查看kgen.generateKey()注释):

JAVA:

private static byte[] getRawKey(byte[] seed) throws Exception {
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
    sr.setSeed(seed); //Seed: [51, 54, 65, 53, 50, 67, 56, 70, 66, 55, 68, 70, 57, 65, 51, 70]
    kgen.init(128, sr); // 192 and 256 bits may not be available
    SecretKey skey = kgen.generateKey(); //skey.key = [-97, -52, 45, -95, -64, -58, 16, -20, 124, -50, -104, 58, 23, -75, 88, 94]
    byte[] raw = skey.getEncoded();
    return raw;
}

安卓系统:

private static byte[] getRawKey(byte[] seed) throws Exception {
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
    sr.setSeed(seed); //Seed: [51, 54, 65, 53, 50, 67, 56, 70, 66, 55, 68, 70, 57, 65, 51, 70]
    kgen.init(128, sr); // 192 and 256 bits may not be available
    SecretKey skey = kgen.generateKey(); //skey.key = [-114, 32, 16, -52, -81, 125, -88, 88, -76, 20, -117, -11, 33, -61, 32, -91]
    byte[] raw = skey.getEncoded();
    return raw;
}

我不是密码学专家,但是如果使用相同的种子,如何可能得到不同的密钥呢?


这很奇怪,但是SecureRandom不应该在不同平台上生成随机数吗?我不确定,我不是一个加密专家。 - Reno
这是一篇旧帖子,但对于那些遇到这个问题的人来说,我发现我的问题出在Android 4.2上,我使用了这个 SO帖子中的答案代码,它运行良好。 - zilinx
有人可以发布一下在iOS中getRawKey()功能的代码吗? - sudheer
4个回答

3
我曾经遇到过同样的问题。解决方法如下:
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
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.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

public class StringEncrypter {

Cipher ecipher;
Cipher dcipher;

StringEncrypter(String password) {

    // 8-bytes Salt
    byte[] salt = {
        (byte)0xA9, (byte)0x9B, (byte)0xC8, (byte)0x32,
        (byte)0x56, (byte)0x34, (byte)0xE3, (byte)0x03
    };

    // Iteration count
    int iterationCount = 19;

    try {

        KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterationCount);
        SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);

        ecipher = Cipher.getInstance(key.getAlgorithm());
        dcipher = Cipher.getInstance(key.getAlgorithm());

        // Prepare the parameters to the cipthers
        AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);

        ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
        dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);

    } catch (InvalidAlgorithmParameterException e) {
        System.out.println("EXCEPTION: InvalidAlgorithmParameterException");
    } catch (InvalidKeySpecException e) {
        System.out.println("EXCEPTION: InvalidKeySpecException");
    } catch (NoSuchPaddingException e) {
        System.out.println("EXCEPTION: NoSuchPaddingException");
    } catch (NoSuchAlgorithmException e) {
        System.out.println("EXCEPTION: NoSuchAlgorithmException");
    } catch (InvalidKeyException e) {
        System.out.println("EXCEPTION: InvalidKeyException");
    }
}


/**
 * Takes a single String as an argument and returns an Encrypted version
 * of that String.
 * @param str String to be encrypted
 * @return <code>String</code> Encrypted version of the provided String
 */
public byte[] encrypt(String str) {
    try {
        // Encode the string into bytes using utf-8
        byte[] utf8 = str.getBytes("UTF8");

        // Encrypt
        byte[] enc = ecipher.doFinal(utf8);

        // Encode bytes to base64 to get a string
        //return new sun.misc.BASE64Encoder().encode(enc);
        return enc;

    } catch (BadPaddingException e) {
    } catch (IllegalBlockSizeException e) {
    } catch (UnsupportedEncodingException e) {
    }
    return null;
}


/**
 * Takes a encrypted String as an argument, decrypts and returns the
 * decrypted String.
 * @param str Encrypted String to be decrypted
 * @return <code>String</code> Decrypted version of the provided String
 */
public String decrypt(byte[] dec) {

    try {

        // Decode base64 to get bytes
        //byte[] dec = new sun.misc.BASE64Decoder().decodeBuffer(str);
        //byte[] dec = Base64Coder.decode(str);

        // Decrypt
        byte[] utf8 = dcipher.doFinal(dec);

        // Decode using utf-8
        return new String(utf8, "UTF8");

    } catch (BadPaddingException e) {
    } catch (IllegalBlockSizeException e) {
    } catch (UnsupportedEncodingException e) {
    }
    return null;
}

我在这里找到了解决方案这个方法很完美。但是我真的想知道,AES有什么问题。


2
"PBEWithMD5AndDES"?大家已经进步了,这不是七十年代。单一DES根本不安全。 - Maarten Bodewes

1

对于那些希望在数据库中使用字符串和Base64编码的人,您可以使用Mike Keskinov(和Anddev.org上的xalien)稍微重写的这些函数。

public String encrypt(String str) 
{
    try {

        // Encode the string into bytes using utf-8
        byte[] utf8 = str.getBytes("UTF8");

        // Encrypt
        byte[] enc = ecipher.doFinal(utf8);

        // Encode bytes to base64 to get a string 
        return new String(Base64.encode(enc,0));

    } catch (BadPaddingException e) {
    } catch (IllegalBlockSizeException e) {
    } catch (UnsupportedEncodingException e) {       
    }
    return null;
}

public String decrypt(String str) 
{
    try {

        // Decode base64 to get bytes           
        byte[] dec = Base64.decode(str, 0);

        // Decrypt
        byte[] utf8 = dcipher.doFinal(dec);

        // Decode using utf-8
        return new String(utf8, "UTF8");

    } catch (BadPaddingException e) {
    } catch (IllegalBlockSizeException e) {
    } catch (UnsupportedEncodingException e) {       
    }
    return null;
}

这段代码还使用了内置的Android Base64库,因此无需导入任何其他外部库。
一开始我没有意识到如何从另一个类中实际使用这个类(我的无知)。以下是一个简单的示例:
StringEncrypter se = new StringEncrypter("mypass");
String decryptedString = se.decrypt(encryptedString);

关于Base64还有一点需要注意。我通过吃亏才发现,当字符串长度超过76个字符时,Base64编码会添加一个换行符(0x0a)。这会严重破坏解密结果。为了去除这些换行符,您可以添加类似以下的内容:

b64_enc_str = se.encrypt(plaintext_str);
str = b64_enc_str.replace("/n","");

希望它能对某人有所帮助。

1

从一般的角度来看,我认为BadPaddingException可能是由以下原因引起的:

i> 加密算法的数据大小不正确。

ii> 使用不同的密钥加密和解密数据。


在我的情况下,它是加密算法的密钥大小。 - Tash Pemhiwa

0

getRawKey假设SecureRandom实例是一个明确定义的、确定性的伪随机数生成器。

首先,它并没有被明确定义;"SHA1PRNG"方法并没有精确定义。即使对于SUN提供商本身,它也可能会发生变化。

其次,直接在构造后对SecureRandom实例进行种子处理使其具有确定性是SUN提供商特定的。换句话说,其他提供商可能选择将种子添加到熵池中。这个熵池可能已经被操作系统获取的值种子化。这在许多版本的Android上都是这样的。用通俗的语言解释:即使在构造后直接进行了种子处理,Android的SecureRandom实例仍然是完全随机的。这意味着在这些系统上,getRawKey方法总是生成一个新的、完全随机的密钥。

那么解决方案是什么?解决方案要么是将密钥存储在KeyStore中,要么是从口令生成密钥。在这种情况下,您可以使用Java SE和Android中已经存在的PBKDF2(基于密码的密钥派生函数#2)功能:

SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// note, the third argument should be set to a value as high as possible
// 10K is about the minimum nowadays
KeySpec ks = new PBEKeySpec(password, salt, 1024, 128);
SecretKey s = f.generateSecret(ks);
Key k = new SecretKeySpec(s.getEncoded(),"AES");

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接