1

I have an app which encrypts some text strings, and then writes these to a file. The desktop version of the app is reading the file and decrypts the data. The problem is that whenever I decrypt on the desktop version , I get a "javax.crypto.BadPaddingException: Given final block not properly padded"

Both the app and the desktop are using the same code:

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class SSL {

    private final static String HEX = "0123456789ABCDEF";

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

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

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

    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 void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
    }
}

Why can't I decrypt the data on the desktop version? Are the crypto implementations in Android SDK and java 1.7 different?

Note: If I decrypt the encrypted android data on the android, it works. If I encrypt and decrypt on the desktop, it also works. The problem seems to be somewhere between those two.

Pphoenix
  • 1,423
  • 1
  • 15
  • 37
  • are you sharing the private key between the desktop and android? If you are not, this would explain why you can encrypt/decrypt on the same device, but not across them. – Farlan Mar 14 '14 at 13:47
  • Yes, I use the same private key :) – Pphoenix Mar 14 '14 at 14:07

2 Answers2

1

I have finally found the whole solution.

There were some major issues, and I would like to explain them here so that more users can find the answer. Firstly, the two things pointed out by Duncan needed to be fixed.

After fixing these issues I still had the same problem, and found out that using a pseudo random number to create the raw key is done differently by different platforms/OS'. If you want to have crossplatform independencies, don't use SHA1PRNG as your key algorithm. Instead, use PBEWithSHA256And256BitAES-CBC-BC. I am using the implementation from BouncyCastle, see below for full crypto code.

import java.io.UnsupportedEncodingException;
import java.security.Security;
import java.security.spec.KeySpec;
import java.util.Random;

import javax.crypto.Cipher;
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.bouncycastle.jce.provider.BouncyCastleProvider;

public class SSL {

    private final static String HEX = "0123456789ABCDEF";
    private final static String ENC = "US-ASCII";
    private final static int ITERATION = 1337;

    private static final String RANDOM_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
    private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final String SECRET_KEY_ALGORITHM = "AES";

    private static IvParameterSpec ips;

    public static void init(byte[] iv) {
        if(iv == null) {
            iv = new byte[16];

            Random random = new Random();
            random.nextBytes(iv);
        }

        ips = new IvParameterSpec(iv);

        Security.addProvider(new BouncyCastleProvider());
    }

    public static byte[] getCertificate() {
        return ips.getIV();
    }

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

    public static String decrypt(Session current, String encrypted) throws Exception {
        byte[] rawKey = getRawKey(current.getCurrentSession().toCharArray());
        byte[] enc = toByte(encrypted);     
        byte[] result = decrypt(rawKey, enc);
        return new String(result, ENC);
    }

    private static byte[] getRawKey(char[] seed) throws Exception {
        KeySpec keySpec = new PBEKeySpec(seed, ips.getIV(), ITERATION);

        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(RANDOM_ALGORITHM);
        byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded();
        SecretKey secretKey = new SecretKeySpec(keyBytes, "AES");

        return secretKey.getEncoded();
    }


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

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

    public static String toHex(String txt) throws UnsupportedEncodingException {
        return toHex(txt.getBytes(ENC));
    }
    public static String fromHex(String hex) throws UnsupportedEncodingException {
        return new String(toByte(hex), ENC);
    }

    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 void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
    }
}
Pphoenix
  • 1,423
  • 1
  • 15
  • 37
  • *"Do not use SHA1PRNG as your key algorithm. Instead, use PBEWithSHA256"* - Oh my, of course not... You need deterministic key generation (there's other ways too, but leave it at that). You should also put an HMAC on the file. The HMAC would have told you the key is wrong without relying on bad padding. Or better, use AES-GCM mode. – jww Sep 26 '14 at 03:55
0

There are at least two problems with your code that will affect its functionality on different platforms:

  1. You must specify a character set when calling getBytes() or new String(...). Without this, the results will be different if your platforms have different default charsets.

  2. You must fully specify your encryption algorithm, e.g. replace "AES" with "AES/CBC/PKCS5Padding" to avoid differences between providers.

Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
  • I know that there are a lot of strange characters when converting to bytes, is there an encoding that will work well for all results or will any encoding work? – Pphoenix Mar 14 '14 at 14:08
  • Best bet is to use hexadecimal if you want to represent Binary data. You can use the US-ASCII char set wirth that. – Duncan Jones Mar 14 '14 at 14:17
  • Hmm, I tried both your solutions. I printed some information, both the IV and the provate key are the same on android as desktop. But, when I encrypt a string they look totally different. The code is updated here: http://pastebin.com/jh6MMKTa Encoding for example "gmail.com" gives F822D09002173D715707A240E4705AC3 on the desktop but 8E4BC76D501BAB2BCBCF28D82992BC37 on android. Data is consistent when transferred from android to desktop. – Pphoenix Mar 16 '14 at 19:51