1

Was trying to decrypt the NFC tag (which contain the exact same value as a QR code generated. The QRCode was able to scan correctly without any error. While the NFC tag which contain the exact same value was throwing BadPaddingException.

String seedValue = "MediLearnerSecurity";
encyrptedResult = "4EA768CA389F4CD688BCCAF11E1E4953"

After checking and retrieving from the tag and barcode scanner. Both are being verified to contain the above encyrptedResult.

Which derive the value 114609X

Some other class

public void decyrption()
{
  try
  {
    deCryptedResult = AESHelper.decrypt(seedValue, encyrptedResult);
  }
  catch (Exception e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
  }
}

AESHelper Class.

 public class AESHelper {

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

        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 final static String HEX = "0123456789ABCDEF";
        private static void appendHex(StringBuffer sb, byte b) {
                sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
        }


    }

LOGCAT

01-21 15:44:25.774: D/TextLayoutCache(11831): Enable myanmar Zawgyi converter
01-21 15:44:31.269: W/System.err(11831): javax.crypto.BadPaddingException: pad block corrupted
01-21 15:44:31.269: W/System.err(11831):    at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:710)
01-21 15:44:31.269: W/System.err(11831):    at javax.crypto.Cipher.doFinal(Cipher.java:1111)
01-21 15:44:31.269: W/System.err(11831):    at nyp.medilearner.methods.AESHelper.decrypt(AESHelper.java:48)
01-21 15:44:31.269: W/System.err(11831):    at nyp.medilearner.methods.AESHelper.decrypt(AESHelper.java:21)
01-21 15:44:31.269: W/System.err(11831):    at com.medilearner.main.ScanSelected.decyrption(ScanSelected.java:148)
01-21 15:44:31.269: W/System.err(11831):    at com.medilearner.main.ScanSelected.onActivityResult(ScanSelected.java:42)
01-21 15:44:31.269: W/System.err(11831):    at android.app.Activity.dispatchActivityResult(Activity.java:5563)
01-21 15:44:31.269: W/System.err(11831):    at android.app.ActivityThread.deliverResults(ActivityThread.java:3496)
01-21 15:44:31.274: W/System.err(11831):    at android.app.ActivityThread.handleSendResult(ActivityThread.java:3543)
01-21 15:44:31.274: W/System.err(11831):    at android.app.ActivityThread.access$1200(ActivityThread.java:159)
01-21 15:44:31.274: W/System.err(11831):    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1364)
01-21 15:44:31.274: W/System.err(11831):    at android.os.Handler.dispatchMessage(Handler.java:99)
01-21 15:44:31.274: W/System.err(11831):    at android.os.Looper.loop(Looper.java:137)
01-21 15:44:31.274: W/System.err(11831):    at android.app.ActivityThread.main(ActivityThread.java:5419)
01-21 15:44:31.274: W/System.err(11831):    at java.lang.reflect.Method.invokeNative(Native Method)
01-21 15:44:31.274: W/System.err(11831):    at java.lang.reflect.Method.invoke(Method.java:525)
01-21 15:44:31.274: W/System.err(11831):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1209)
01-21 15:44:31.279: W/System.err(11831):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1025)
01-21 15:44:31.279: W/System.err(11831):    at dalvik.system.NativeStart.main(Native Method)
Duncan Jones
  • 67,400
  • 29
  • 193
  • 254
Walalala
  • 77
  • 8
  • which version is invoked? The `String` or the `byte[]`? You did not tell us of which type `seedValue` and `encryptedResult` are. How did you verify the input is the same for the two (QR and NFC)? – Fildor Jan 21 '14 at 07:54
  • 2
    Note that you should never use `getBytes()` without specifying a charset, otherwise you may get different results on different platforms. Also, don't specify just `"AES"` as your algorithm otherwise you're leaving the padding and cipher mode as a choice for your provider. Specify instead the entire string, e.g. `"AES/CBC/PKCS5Padding"`. – Duncan Jones Jan 21 '14 at 08:37
  • Use `CypherInputStream` and `CypherOutputStream`, that way you don't need to care about padding anymore. – Mister Smith Jan 21 '14 at 09:56
  • I notice the issue is caused by different in OS version. android 4.2 and above would return a totally different solution. Just wondering what's the alternate solution to using byte? The PKCS5Padding doesn't work as it's an OS issue. @Duncan – Walalala Jan 22 '14 at 02:54
  • @MisterSmith Why wouldn't padding be a concern? You have to specify a choice of padding when you construct a `CipherOutputStream`. – Duncan Jones Jan 22 '14 at 07:53
  • @Duncan the [CipherOutputStream constructor](http://developer.android.com/reference/javax/crypto/CipherOutputStream.html#CipherOutputStream%28java.io.OutputStream,%20javax.crypto.Cipher%29) takes a `Cipher` as a second param. That's way easier than using the array approach, where you need to create arrays of the correct block size, and this requires knowledge of the algorithm. It's way easier to use the streams, but seems that most examples in the web use the more difficult array approach. – Mister Smith Jan 22 '14 at 11:58
  • @MisterSmith What is the array approach you describe? As far as I'm concerned, streams are no easier than using `Cipher` objects directly. Padding of input data is always an issue, but most often it's handled by declaring a padding scheme in your `Cipher` object (whether used directly or via a stream). – Duncan Jones Jan 22 '14 at 12:06
  • @Duncan The array approach is using the cipher directly. As most examples in the web use short strings as input, nobody cares about creating streams in their hello world snippet. But when the input grows in size problems often arise. Just look how many questions about `BadPaddingException` are in SO. Streams are easier to use but newbies usually don't know about them. – Mister Smith Jan 22 '14 at 12:37
  • @MisterSmith I completely disagree. `BadPaddingException`s have *nothing* to do with whether streams are used or not. Streams are just a different way of feeding data into a `Cipher` object. Sure, this might be a better approach than throwing a gigabyte of data into the `doFinal()` method, but it has nothing to do with those exceptions. – Duncan Jones Jan 22 '14 at 12:40
  • @Duncan Thats not what I meant. Obviously if used correctly the array approach works, but it is less intuitive for people copy/pasting a hello world snippet. – Mister Smith Jan 22 '14 at 12:48
  • 1
    Very often BadPaddingException just means that the used decryption key is invalid. Without padding you just don't recognize that the decryption failed - but the output is just garbage in such a case. – Robert Jan 22 '14 at 12:52
  • @MisterSmith I'm just pointing out that you've failed to highlight *why* streams help avoid `BadPaddingException`s (I don't believe they do). And [your original comment](http://stackoverflow.com/questions/21252078/aes-encryption-badpadding?noredirect=1#comment32020269_21252078) is just wrong. – Duncan Jones Jan 22 '14 at 13:00
  • @Duncan Maybe my first comment goes a bit too far. I just mean using a `Cipher` (once configured) correctly is harder than using cipherstreams. Explanation below. – Mister Smith Jan 22 '14 at 13:51
  • @Duncan snippets usually show a single part transformation, but in production you usually need to perform a multi-part one. When implementing this, you need to know about the `update` method (not usually shown in examples). Versions of `update` and `doFinal` take buffers as arguments that need to be of the correct size for the algorithm (otherwise you get IllegalBlockSizeException, ShortBufferException). Input buffers should be properly padded (otherwise you get BadPaddingException). And integer parameters for these methods (offsets, lengths) should be valid as well. Streams are easier. – Mister Smith Jan 22 '14 at 13:55
  • @MisterSmith Most people (and most examples on the net) create a `Cipher` instance that incorporates a padding scheme. The standard Sun providers will give you `"AES/ECB/PKCS5Padding"` if you ask for just `"AES"`. Therefore you can supply arbitrary length data to `update` and `doFinal` and you will not get a padding-related exception. Therefore I still don't see why using a `Cipher*putStream` is any easier. – Duncan Jones Jan 22 '14 at 14:27
  • @MisterSmith FYI, our comment thread has grown rather long so I [created a chat roomt](http://chat.stackoverflow.com/rooms/45808/discussion-about-http-stackoverflow-com-questions-21252078-aes-encryption-badpa) if you want to debate this further. – Duncan Jones Jan 22 '14 at 14:37

1 Answers1

1

I assume that one of your problems is located in the byte[] getRawKey(byte[] seed).

If I understand it correctly this method assumes that you always get the same key for a given seed.

However this is not the case as it uses a SHA1PRNG - and such pseudo random generators are not standardized. Therefore if you only use Java SE 1.6 for generating a key you will always get the same result. But the SHA1PRNG algorithm in other programming languages, alternative Java platforms like Android or even other Java versions/libraries my be get a different result.

Read also: javax.crypto working differently in different versions of Android OS?

Conclusion: Never misuse a KeyGenerator and/or a SecureRandom for creating keys. The only usage of these classes is to create new random keys! The current implementation you have posted is totally broken.

Community
  • 1
  • 1
Robert
  • 39,162
  • 17
  • 99
  • 152
  • Good answer. Make the changes Robert suggests, plus the ones I [suggested in the comments](http://stackoverflow.com/questions/21252078/aes-encryption-badpadding?noredirect=1#comment32017503_21252078) and you'll probably have a working solution. – Duncan Jones Jan 22 '14 at 08:03
  • @user3040999: My answer is the reason why you noticed the issue on android 4.2 and above - the SHA1PRNG has changed... – Robert Jan 22 '14 at 12:47