3

We need to write some Android code to decrypt some data sent from our server. Our server team gave us some sample decryption code which uses the "SunJCE" provider, which unfortunately doesn't exist on Android.

Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");

Does anybody know the cleanest way to implement this on Android? If we try this on Android

Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");

then it looks like some unwanted garbage appears at the end of the decrypted text, e.g:

ComparisonFailure: expected:<...lAAAAABJRU5ErkJggg==[]> but was:<...lAAAAABJRU5ErkJggg==[��������]>    

We tried lots of combinations of different transformations in the Android Cipher class (e.g. "AES/CBC/PKCS5Padding"), but kept hitting things like BadPaddingExceptions.

We were also able to decrypt this data using a Java-only module, which didn't appear to show the same garbage characters. Is there a way to do this using just Android classes?

Dan J
  • 25,433
  • 17
  • 100
  • 173
  • Could you print out the plaintext value (using NoPadding) in hexadecimals? Bouncy Castle and the Apache Commons Codec (and Guava etc. etc.) have a hexadecimal encoder. – Maarten Bodewes Dec 21 '15 at 19:55
  • Possible duplicate of [Simple java AES encrypt/decrypt example](http://stackoverflow.com/questions/15554296/simple-java-aes-encrypt-decrypt-example) – JFPicard Dec 21 '15 at 19:57
  • Have you tried the example [simple-java-aes-encrypt-decrypt-example](http://stackoverflow.com/questions/15554296/simple-java-aes-encrypt-decrypt-example)? – caot Dec 21 '15 at 20:11
  • My situation is a bit different to the "Simple java AES encrypt/decrypt example" question, as our Android client is only decrypting, not encrypting. We can encrypt and decrypt fine on Android (and when testing a Java-only standalone project that does the same thing), but we get garbage at the end on Android when decrypting the data that was encrypted on the Java JVM. I believe the garbage bytes at the end are all zeros. My encrypted data is defined in a Java String, as is the expected plaintext after decryption (that is taken from the sample Java-only project). – Dan J Dec 21 '15 at 22:35

3 Answers3

3

The same garbage is also present in the Java code. It's just that you probably run this on Windows that uses the default Latin (ISO_8859_1) character set and that Android uses UTF-8 by default. It also depends on the console and font used to print out the characters. In this case it is likely that the padding that is used doesn't print on the Windows console but does on the Android code.

You need to see the byte array (e.g. in hexadecimals) to find out which padding is used and then strip it off before turning the plaintext into a string.

Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
1

@Maarten Bodewes was correct: the garbage characters are present in our Java-only module too.

The problem is that our server side code is padding the input data with zeros before encrypting it. This is so the input matches the block size required for AES.

There's a good discussion here: Android - Removing padded bits in decryption

Even though I'm a little uneasy using this "zero padding", here's a utility I wrote to remove it:

public static String getStringAfterRemovingZeroPadding(byte[] input) {
    if (input == null) {
        return null;
    }

    int index = input.length - 1;

    while (index >= 0) {
        if (input[index] == 0) {
            // We found some zero padding, look at the next character and see if it's also zero
            // padding
            --index;
        } else {
            // This character is not a zero padding, so go back to the zero padding that we
            // just inspected, or go to the end of the string
            ++index;
            break;
        }
    }

    if (index < 0) {
        return "";
    }

    return new String(input, 0, index);
}

...and here are my unit tests:

@Test
public void testRemoveZeroPaddingNull() throws Exception {
    String result = StringUtils.getStringAfterRemovingZeroPadding(null);
    assertThat(result).isNull();
}

@Test
public void testRemoveZeroPaddingAllZeros() throws Exception {
    byte[] input = {0, 0};
    String result = StringUtils.getStringAfterRemovingZeroPadding(input);
    assertThat(result).isEqualTo("");
}

@Test
public void testRemoveZeroPaddingNoZeros() throws Exception {
    byte[] input = {80, 80, 80};
    String result = StringUtils.getStringAfterRemovingZeroPadding(input);
    assertThat(result).isEqualTo("PPP");
}

@Test
public void testRemoveZeroPaddingOneZero() throws Exception {
    byte[] input = {80, 80, 80, 0};
    String result = StringUtils.getStringAfterRemovingZeroPadding(input);
    assertThat(result).isEqualTo("PPP");
}

@Test
public void testRemoveZeroPaddingSomeZeros() throws Exception {
    byte[] input = {80, 80, 80, 0, 0, 0, 0, 0};
    String result = StringUtils.getStringAfterRemovingZeroPadding(input);
    assertThat(result).isEqualTo("PPP");
}
Community
  • 1
  • 1
Dan J
  • 25,433
  • 17
  • 100
  • 173
-1

If you run this code on Android, you'll see what Ciphers are supported:

TreeSet<String> ciphers = new TreeSet<>();
for (Provider provider : Security.getProviders())
    for (Service service : provider.getServices())
        if (service.getType().equals("Cipher"))
            ciphers.add(service.getAlgorithm());
for (String cipher : ciphers)
    System.out.println(cipher);

On Windows 7 with JDK 1.8.0_51, I get:

AES
AESWrap
AESWrap_128
AESWrap_192
AESWrap_256
AES_128/CBC/NoPadding
AES_128/CFB/NoPadding
AES_128/ECB/NoPadding
AES_128/GCM/NoPadding
AES_128/OFB/NoPadding
AES_192/CBC/NoPadding
AES_192/CFB/NoPadding
AES_192/ECB/NoPadding
AES_192/GCM/NoPadding
AES_192/OFB/NoPadding
AES_256/CBC/NoPadding
AES_256/CFB/NoPadding
AES_256/ECB/NoPadding
AES_256/GCM/NoPadding
AES_256/OFB/NoPadding
ARCFOUR
Blowfish
DES
DESede
DESedeWrap
PBEWithHmacSHA1AndAES_128
PBEWithHmacSHA1AndAES_256
PBEWithHmacSHA224AndAES_128
PBEWithHmacSHA224AndAES_256
PBEWithHmacSHA256AndAES_128
PBEWithHmacSHA256AndAES_256
PBEWithHmacSHA384AndAES_128
PBEWithHmacSHA384AndAES_256
PBEWithHmacSHA512AndAES_128
PBEWithHmacSHA512AndAES_256
PBEWithMD5AndDES
PBEWithMD5AndTripleDES
PBEWithSHA1AndDESede
PBEWithSHA1AndRC2_128
PBEWithSHA1AndRC2_40
PBEWithSHA1AndRC4_128
PBEWithSHA1AndRC4_40
RC2
RSA
RSA/ECB/PKCS1Padding
Andreas
  • 154,647
  • 11
  • 152
  • 247