Wednesday, March 16, 2016

Android 6 crypto Cipher.update doesn't work anymore like Android 5

Leave a Comment

The following code worked perfectly on Android 5, now on Android 6 I have this assert error:

junit.framework.ComparisonFailure: expected:

This is clear te[xt right now]

but was:

This is clear te[]

at testAndroidAesCfbDecrypther(AesCfbCryptherTest.java:112)

This function works on Motorola Moto G Android 5.1, Samsunsg S5 Android 5.1 and emulator with Android 5.1. It doesn't work on Motorola Moto G Android 6 and emulator with Android 6.

public void testAndroidAesCfbDecrypther() {    Cipher AESCipher;   final String password = "th3ke1of16b1t3s0"; //password   final byte[] IV = Hex.toBytes("aabbccddeeff3a1224420b1d06174748"); //vector    final String expected = "This is clear text right now";   final byte[] encrypted1 = Hex.toBytes("a1ea8e1c4d8579b84e3e8d48d17fe916a70079b1bdc75841667cc15f");   final byte[] encrypted2 = Hex.toBytes("73052b25306059dda5d6880aa873383124448a38bcb3a769f6aed2f5");    try {         byte[] key = password.getBytes("US-ASCII");         key = Arrays.copyOf(key, 16); // use only first 128 bit           SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");          IvParameterSpec IVSpec = new IvParameterSpec(IV);          AESCipher = Cipher.getInstance("AES/CFB/NoPadding"); //Tried also with and without "BC" provider          AESCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IVSpec);          byte[] dec1 = AESCipher.update(encrypted1);         String r = new String(dec1);         assertEquals(expected, r); //assert fail here          byte[] dec2 = AESCipher.update(encrypted2);         r = new String(dec2);         assertEquals(expected, r);    } catch (NoSuchAlgorithmException e) {         ...   }  } 

For testing purposes i tried also with 'doFinal', but second assertion fails:

ByteArrayOutputStream bytesStream1 = new ByteArrayOutputStream();  byte[] dec1 = AESCipher.update(encrypted1); bytesStream1.write(dec1); byte[] dec2 = AESCipher.doFinal(); bytesStream1.write(dec2);  r = new String(bytesStream1.toByteArray()); assertEquals(expected, r); //ASSERTION OKAY  ByteArrayOutputStream bytesStream2 = new ByteArrayOutputStream();  dec1 = AESCipher.update(encrypted2); bytesStream2.write(dec1); dec2 = AESCipher.doFinal(); bytesStream2.write(dec2);  r = new String(bytesStream2.toByteArray()); assertEquals(expected, r); //ASSERTION FAIL 

Just as a test I tried the same thing in ruby and it works:

require 'openssl'  expected = "This is clear text right now" encrypted1 = ["a1ea8e1c4d8579b84e3e8d48d17fe916a70079b1bdc75841667cc15f"].pack('H*') encrypted2 = ["73052b25306059dda5d6880aa873383124448a38bcb3a769f6aed2f5"].pack('H*')  decipher = OpenSSL::Cipher.new('AES-128-CFB') decipher.decrypt decipher.key = "th3ke1of16b1t3s0" #password decipher.iv = ["aabbccddeeff3a1224420b1d06174748"].pack('H*') #vector  puts "TEST1-------------------" puts (decipher.update(encrypted1) + decipher.final) == expected ? "OK" : "FAIL" puts "------------------------"  puts "TEST2-------------------" puts (decipher.update(encrypted2) + decipher.final) == expected ? "OK" : "FAIL" puts "------------------------" 

2 Answers

Answers 1

Block ciphers have many different modes of operation. Some like CBC require an additional padding, because only multiples of the block size can be encrypted, but others like CFB are streaming modes without padding.

If you use padding, then the contract is that full blocks are returned from Cipher#update, but the last block that must be padded or unpadded, can only be returned from Cipher#doFinal.

Since CFB mode doesn't need padding, it really shouldn't have this restriction, but then you would have changed the contract, because now Cipher#update can return incomplete data. If this contract is to be enforced even for CFB mode, then the implementation will be consistent and possibly even easier (because of intermediate values and the shift register of CFB).

You really need to finish the decryption and combine the output yourself. It's easy to do this with a ByteArrayOutputStream, but you can also use three System.arraycopy calls.

ByteArrayOutputStream fullPlaintextStream = new ByteArrayOutputStream();  byte[] dec1 = AESCipher.update(encrypted1); fullPlaintextStream.write(dec1);  byte[] dec2 = AESCipher.update(encrypted2); fullPlaintextStream.write(dec2);  byte[] dec3 = AESCipher.doFinal(); fullPlaintextStream.write(dec3);  r = new String(fullPlaintextStream.toByteArray()); assertEquals(expected, r); 

Answers 2

Tried to run the code in java - jdk 1.6 but it fails. Below is what I tried, if its of any help - (modified to be able to run in eclipse by default) :

public static void testAndroidAesCfbDecrypther() {      Cipher AESCipher;     final String password = "th3ke1of16b1t3s0"; //password     final byte[] IV = DatatypeConverter.parseHexBinary("aabbccddeeff3a1224420b1d06174748"); //vector      final String expected = "This is clear text right now";     final byte[] encrypted1 = DatatypeConverter.parseHexBinary("a1ea8e1c4d8579b84e3e8d48d17fe916a70079b1bdc75841667cc15f");     final byte[] encrypted2 = DatatypeConverter.parseHexBinary("73052b25306059dda5d6880aa873383124448a38bcb3a769f6aed2f5");      try {         byte[] key = password.getBytes("US-ASCII");         key = Arrays.copyOf(key, 16); // use only first 128 bit           SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");          IvParameterSpec IVSpec = new IvParameterSpec(IV);          AESCipher = Cipher.getInstance("AES/CFB/NoPadding"); //Tried also with and without "BC" provider          AESCipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IVSpec);          byte[] dec1 = AESCipher.update(encrypted1);         String r = new String(dec1);         assertEquals(expected, r); //assert fail here          byte[] dec2 = AESCipher.update(encrypted2);         r = new String(dec2);         assertEquals(expected, r);      } catch (NoSuchAlgorithmException e) {         e.printStackTrace();     } catch (UnsupportedEncodingException e) {         e.printStackTrace();     } catch (NoSuchPaddingException e) {         e.printStackTrace();     } catch (InvalidKeyException e) {         e.printStackTrace();     } catch (InvalidAlgorithmParameterException e) {         e.printStackTrace();     }  }  private static void assertEquals(String left, String right) {     System.out.println(left+":"+right);     System.out.println(left.equals(right)); } 

Output :

This is clear text right now:This is clear te false This is clear text right now:xt right nowThis is clear text r false 

May be the default buffer sizes changed.

Can you run the above in the two emulators and post the same ?


Also below code helps in identifying the CipherSpi implementation used (assuming security manager does not complain) :

private static void printCipherDetails(Cipher cipher) {     try {         for(Field field : cipher.getClass().getDeclaredFields() ){             field.setAccessible(true);              if( field.getType() == javax.crypto.CipherSpi.class ) {                 Object object = field.get(cipher);                 System.out.print("Name :"+field.getName()+". ");                 if( object != null ) {                     System.out.println("CipherSpi :"+object.getClass());                     }                 else {                     System.out.println("CipherSpi not initialized!");                 }             }             else if(  field.getType() == java.security.Provider.class ) {                 Object object = field.get(cipher);                 System.out.print("Name :"+field.getName()+". ");                 if( object != null ) {                     System.out.println("Provider :"+object.getClass());                  }                 else {                     System.out.println("Provider not initialized!");                 }             }         }     }catch (Exception e) {         e.printStackTrace();     }     System.out.println(""); } 

When invoked after cipher.init(), prints details such as noted below :

Name :b. Provider :class com.sun.crypto.provider.SunJCE Name :c. CipherSpi :class com.sun.crypto.provider.AESCipher Name :j. CipherSpi not initialized! 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment