BadPaddingException in Android encrypt

2020-06-04 13:42发布

问题:

I am making an Android application and I want to encrypt a String before sending it to a DataBase, and encrytpion is correct. The problem happens when decrypting the String because I get a BadPaddingException and I have no idea where the problem is. Here is the code:

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));
}

I encrypt and decrypt with this code:

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

Can anyone help me please?

Thank you very much!!

EDIT: Problem is not solved, but I have a bit more information. First of all, I encrypt in a Java project, and I decrypt in an Android project. I have tried to decrypt in the same Java project, and there is no problem, but if I try to decrypt in Android, it doesn't work. The problem is in method "getRawKey" (look at the kgen.generateKey() comment):

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;
}

ANDROID:

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;
}

I am not a crypt expert, but how can be possible that with the same seed, I get a different key??

回答1:

I had the some problem. The solution:

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;
}

}

I found this solution here. This works perfect. But I really wonder to know, what was the problem with AES.



回答2:

For those who wish to use String and Base64-encoding for usage with a database you can use these (slightly rewritten) functions from Mike Keskinov (and xalien on Anddev.org).

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;
}

This code also uses the built-in Android Base64 libraries, so no need for importing any additional external libraries.

Something I didn't realise at first was how to actually use this class from another class (my ignorance). Here's a simple example:

StringEncrypter se = new StringEncrypter("mypass");
String decryptedString = se.decrypt(encryptedString);

One more thing on Base64. I found out the hard way that Base64 encoding adds a '/n' linefeed character (0x0a) whenever the string gets longer than 76 characters. This severely messes up the decrypt results. To strip out these newline characters you can add something like:

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

Hopefully it helps someone.



回答3:

From the general perspective , I think BadPaddingException can be due to the following reasons:

i> Incorrect size of the data for the encryption algorithm.

ii> Different keys are being used to encrypt & decrypt data.



回答4:

getRawKey assumes that the SecureRandom instance is a well defined, deterministic Pseudo Random Number Generator.

First of all, it isn't well defined; the "SHA1PRNG" method is not precisely defined. It may - and has - changed even for the SUN provider itself.

Second, the fact that seeding the SecureRandom instance directly after construction makes it deterministic is SUN provider specific. In other words, other providers may choose to add the seed to the entropy pool. This entropy pool may have already been seeded with values obtained from the operating system. This is the case on many versions of Android. To explain it in layman terms: the Android SecureRandom instance is fully random even if seeded directly after construction. This means that the getRawKey method on such systems always generates a new, completely random key.

So what's the solution? The solution is either to store the key in a KeyStore or to generate a key from a passphrase. In that case you may use the PBKDF2 (password based key derivation function #2) functionality already present in java SE and Android:

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");