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