Android 4.2 rompió mi código de cifrado / descifrado y las soluciones proporcionadas no funcionan

En primer lugar, ya he visto que Android 4.2 rompió mi código AES de cifrado / descifrado y el error de cifrado en Android 4.2 y la solución proporcionada:

SecureRandom sr = null; if (android.os.Build.VERSION.SDK_INT >= JELLY_BEAN_4_2) { sr = SecureRandom.getInstance("SHA1PRNG", "Crypto"); } else { sr = SecureRandom.getInstance("SHA1PRNG"); } 

no funciona para mí, porque cuando decodifico datos encriptados en Android <4.2 en Android 4.2, obtengo:

 javax.crypto.BadPaddingException: pad block corrupted at com.android.org.bouncycastle.jcajce.provider.symmetric.util.BaseBlockCipher.engineDoFinal(BaseBlockCipher.java:709) 

Mi código es bastante simple, y funcionaba hasta Android 4.2:

 public static byte[] encrypt(byte[] data, String seed) throws Exception { KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG"); secrand.setSeed(seed.getBytes()); keygen.init(128, secrand); SecretKey seckey = keygen.generateKey(); byte[] rawKey = seckey.getEncoded(); SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, skeySpec); return cipher.doFinal(data); } public static byte[] decrypt(byte[] data, String seed) throws Exception { KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG"); secrand.setSeed(seed.getBytes()); keygen.init(128, secrand); SecretKey seckey = keygen.generateKey(); byte[] rawKey = seckey.getEncoded(); SecretKeySpec skeySpec = new SecretKeySpec(rawKey, "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, skeySpec); return cipher.doFinal(data); } 

Supongo que el proveedor predeterminado no fue lo único que cambió en Android 4.2, de lo contrario mi código funcionaría con la solución propuesta.

Mi código se basó en una publicación que encontré aquí en StackOverflow hace mucho tiempo; Veo que difiere de las publicaciones mencionadas, ya que solo crea criptas y descifra las matrices de bytes, mientras que las otras soluciones criptan y descifran cadenas (HEX Strings, creo).

¿Tiene que ver con la semilla? ¿Tiene una longitud mínima / máxima, restricción de caracteres, etc.?

¿Alguna idea / solución?

EDITAR : Después de muchas pruebas, veo que hay 2 problemas:

  1. El proveedor cambió en Android 4.2 (API 17) -> Este es fácil de solucionar, solo aplica la solución que mencioné en la parte superior de la publicación

  2. BouncyCastle cambió de 1.34 a 1.45 en Android 2.2 (API 8) -> Android2.3 (API 9), por lo que el problema de descifrado que he mencionado anteriormente es el mismo que se describe aquí: BouncyCastle Error de AES al actualizar a 1.45

Así que ahora la pregunta es: ¿hay alguna manera de recuperar los datos cifrados en BouncyCastle 1.34 en BouncyCastle 1.45+?

Primero un descargo de responsabilidad:

¡NO use nunca “SecureRandom” para obtener una clave! ¡Esto está roto y no tiene sentido!

Si estás leyendo una clave AES del disco, simplemente almacena la clave real y no pases por este extraño baile. Puede obtener un SecretKey para el uso de AES de los bytes haciendo:

  SecretKey key = new SecretKeySpec(keyBytes, "AES"); 

Si está utilizando una contraseña para derivar una clave, siga el excelente tutorial de Nelenkov con la advertencia de que una buena regla general es que el tamaño de la sal debe ser del mismo tamaño que el resultado clave. Se parece a esto:

  /* User types in their password: */ String password = "password"; /* Store these things on disk used to derive key later: */ int iterationCount = 1000; int saltLength = 32; // bytes; should be the same size as the output (256 / 8 = 32) int keyLength = 256; // 256-bits for AES-256, 128-bits for AES-128, etc byte[] salt; // Should be of saltLength /* When first creating the key, obtain a salt with this: */ SecureRandom random = new SecureRandom(); byte[] salt = new byte[saltLength]; random.nextBytes(salt); /* Use this to derive the key from the password: */ KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterationCount, keyLength); SecretKeyFactory keyFactory = SecretKeyFactory .getInstance("PBKDF2WithHmacSHA1"); byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded(); SecretKey key = new SecretKeySpec(keyBytes, "AES"); 

Eso es. Cualquier otra cosa que no deberías usar.

El problema es que con el nuevo proveedor , el siguiente fragmento de código

 KeyGenerator keygen = KeyGenerator.getInstance("AES"); SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG"); secrand.setSeed(seed.getBytes()); keygen.init(128, secrand); SecretKey seckey = keygen.generateKey(); byte[] rawKey = seckey.getEncoded(); 

genera una rawKey diferente y genuinamente aleatoria cada vez que se ejecuta. Entonces, intenta descifrar con una clave diferente a la utilizada para cifrar datos y obtiene la excepción. No podrá recuperar su clave o datos cuando se haya generado de esta manera, y solo se haya guardado la semilla .

Lo que lo solucionó para mí (como sugirió @Giorgio) fue simplemente reemplazar esto :

 SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG"); 

con esto :

 SecureRandom secrand = SecureRandom.getInstance("SHA1PRNG", "Crypto"); 
 private static final int ITERATION_COUNT = 1000; private static final int KEY_LENGTH = 256; private static final String PBKDF2_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA1"; private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding"; private static final int PKCS5_SALT_LENGTH = 32; private static final String DELIMITER = "]"; private static final SecureRandom random = new SecureRandom(); public static String encrypt(String plaintext, String password) { byte[] salt = generateSalt(); SecretKey key = deriveKey(password, salt); try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); byte[] iv = generateIv(cipher.getBlockSize()); IvParameterSpec ivParams = new IvParameterSpec(iv); cipher.init(Cipher.ENCRYPT_MODE, key, ivParams); byte[] cipherText = cipher.doFinal(plaintext.getBytes("UTF-8")); if(salt != null) { return String.format("%s%s%s%s%s", toBase64(salt), DELIMITER, toBase64(iv), DELIMITER, toBase64(cipherText)); } return String.format("%s%s%s", toBase64(iv), DELIMITER, toBase64(cipherText)); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } public static String decrypt(String ciphertext, String password) { String[] fields = ciphertext.split(DELIMITER); if(fields.length != 3) { throw new IllegalArgumentException("Invalid encypted text format"); } byte[] salt = fromBase64(fields[0]); byte[] iv = fromBase64(fields[1]); byte[] cipherBytes = fromBase64(fields[2]); SecretKey key = deriveKey(password, salt); try { Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM); IvParameterSpec ivParams = new IvParameterSpec(iv); cipher.init(Cipher.DECRYPT_MODE, key, ivParams); byte[] plaintext = cipher.doFinal(cipherBytes); return new String(plaintext, "UTF-8"); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } catch (UnsupportedEncodingException e) { throw new RuntimeException(e); } } private static byte[] generateSalt() { byte[] b = new byte[PKCS5_SALT_LENGTH]; random.nextBytes(b); return b; } private static byte[] generateIv(int length) { byte[] b = new byte[length]; random.nextBytes(b); return b; } private static SecretKey deriveKey(String password, byte[] salt) { try { KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, ITERATION_COUNT, KEY_LENGTH); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(PBKDF2_DERIVATION_ALGORITHM); byte[] keyBytes = keyFactory.generateSecret(keySpec).getEncoded(); return new SecretKeySpec(keyBytes, "AES"); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } private static String toBase64(byte[] bytes) { return Base64.encodeToString(bytes, Base64.NO_WRAP); } private static byte[] fromBase64(String base64) { return Base64.decode(base64, Base64.NO_WRAP); } 

Fuente

No puedo darle una respuesta a su pregunta, pero simplemente trataría de trabajar en esto> – si enfrenta algunos problemas con bouncycastle en dispositivos / versión de sistema operativo, debería deshacerse completamente de las versiones incorporadas y en su lugar agregar Bouncycastle como jar a su proyecto, cambie su import para que apunte a ese flask, reconstruya y suponiendo que todo funciona, usted sería inmune a los cambios en la versión incorporada de Android a partir de ahora.

Como todo esto no me ayudó a generar una contraseña encriptada que era determinista en todos los dispositivos Android (> = 2.1), busqué otra implementación AES. Encontré uno que funciona para mí en todos los dispositivos. No soy un especialista en seguridad, por lo tanto, no rechace mi respuesta si la técnica no es tan segura como podría ser. Solo estoy publicando el código para las personas que se han encontrado con el mismo problema que tuve antes.

 import java.security.GeneralSecurityException; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import android.util.Log; public class EncodeDecodeAES { private static final String TAG_DEBUG = "TAG"; private IvParameterSpec ivspec; private SecretKeySpec keyspec; private Cipher cipher; private String iv = "fedcba9876543210";//Dummy iv (CHANGE IT!) private String SecretKey = "0123456789abcdef";//Dummy secretKey (CHANGE IT!) public EncodeDecodeAES() { ivspec = new IvParameterSpec(iv.getBytes()); keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES"); try { cipher = Cipher.getInstance("AES/CBC/NoPadding"); } catch (GeneralSecurityException e) { Log.d(TAG_DEBUG, e.getMessage()); } } public byte[] encrypt(String text) throws Exception { if (text == null || text.length() == 0) throw new Exception("Empty string"); byte[] encrypted = null; try { cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec); encrypted = cipher.doFinal(padString(text).getBytes()); } catch (Exception e) { Log.d(TAG_DEBUG, e.getMessage()); throw new Exception("[encrypt] " + e.getMessage()); } return encrypted; } public byte[] decrypt(String code) throws Exception { if (code == null || code.length() == 0) throw new Exception("Empty string"); byte[] decrypted = null; try { cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec); decrypted = cipher.doFinal(hexToBytes(code)); } catch (Exception e) { Log.d(TAG_DEBUG, e.getMessage()); throw new Exception("[decrypt] " + e.getMessage()); } return decrypted; } public static String bytesToHex(byte[] data) { if (data == null) { return null; } int len = data.length; String str = ""; for (int i = 0; i < len; i++) { if ((data[i] & 0xFF) < 16) str = str + "0" + java.lang.Integer.toHexString(data[i] & 0xFF); else str = str + java.lang.Integer.toHexString(data[i] & 0xFF); } return str; } public static byte[] hexToBytes(String str) { if (str == null) { return null; } else if (str.length() < 2) { return null; } else { int len = str.length() / 2; byte[] buffer = new byte[len]; for (int i = 0; i < len; i++) { buffer[i] = (byte) Integer.parseInt(str.substring(i * 2, i * 2 + 2), 16); } return buffer; } } private static String padString(String source) { char paddingChar = ' '; int size = 16; int x = source.length() % size; int padLength = size - x; for (int i = 0; i < padLength; i++) { source += paddingChar; } return source; } } 

Puedes usarlo como:

 EncodeDecodeAES aes = new EncodeDecodeAES (); /* Encrypt */ String encrypted = EncodeDecodeAES.bytesToHex(aes.encrypt("Text to Encrypt")); /* Decrypt */ String decrypted = new String(aes.decrypt(encrypted)); 

Fuente: AQUÍ

Tiene que ver con la semilla de hecho y también debe usar múltiples de 8 (como 8, 16, 24 o 32), intente completar la semilla con A’s y B’s o 1’s y 0s (tiene que ser algo como esto ABAB … ., porque AAA .. o BBB .. tampoco funcionarán.) hasta alcanzar un múltiplo de 8. Hay otra cosa si estás leyendo y encriptando solo bytes, (no convirtiéndolo en Char64 como yo lo hice), entonces necesitas un relleno PKCS5 o PKCS7 apropiado, sin embargo en tu caso (debido solo a 128bits y ha sido creado con versiones de Android) PKCS5 sería suficiente, aunque también debería poner en su SecreteKeySpec algo así como “AES / CBC / PKCS5Padding” o “AES / ECB / PKCS5Padding” en lugar de simplemente “AES”, porque Android 4.2 está usando PKCS7Padding por defecto y si solo son bytes, realmente necesita el mismo algoritmo que antes era el predeterminado. Intente obtener un dispositivo con un Android anterior a 4.2. Compruebe el árbol de Objetos en su ” keygen.init (128, secrand); ” si no me equivoco, tiene el cifrado de la etiqueta, que usarlo. Darle una oportunidad.