Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


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

En primer lugar, ya he visto Android 4.2 rompió mi código AES cifrar / descifrar 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, al decodificar datos cifrados en Android <4.2 en Android 4.2, obtengo:

  • ¿Podemos instanciar una clase abstracta?
  • Google Glass GDK: cómo comunicarse con el dispositivo Android
  • ¿Cómo obtengo el archivo de registro desde un dispositivo Android?
  • Análisis de archivos XML locales con Sax en Android
  • Activador de jabón, fallo de compilación de maven
  • Error al ejecutar zipalign
  •  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 estaba funcionando 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); } 

    Mi conjetura es que el proveedor por defecto no era lo único que cambiaba en Android 4.2, de lo contrario mi código funcionaría con la solución propuesta.

    Mi código se basó en un post que encontré aquí en StackOverflow hace mucho tiempo; Veo que se diferencia de los puestos mencionados, ya que sólo las criptas y decodifica arrays de bytes, mientras que las otras soluciones de criptar y descifrar Cuerdas (Cuerdas HEX, creo).

    ¿Tiene que ver con la semilla? ¿Tiene una longitud min / max, restricción de caracteres, etc?

    Alguna idea / solución?

    EDIT : Después de un montón de pruebas, veo que hay 2 problemas:

    1. El proveedor ha cambiado en Android 4.2 (API 17) -> Este es fácil de arreglar, sólo tiene que aplicar la solución que he mencionado 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 dicho anteriormente es el mismo que se describe aquí: BouncyCastle error 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+?

  • Cómo capturar la dirección del movimiento de los dedos en el teléfono Android?
  • Paso de arraylist de objetos entre actividades
  • ¿Cómo eliminar la aplicación de ejemplo del emulador de Android?
  • Android nGPS: obtener ubicación basada en el campo magnético en lugar de gps o triangulación de células
  • Barra de herramientas que no se muestra con desplazamiento para actualizar
  • Android Excepción: LoadedApk: forgetServiceDispatcher: 888 {AdWorker # 2
  • 7 Solutions collect form web for “Android 4.2 rompió mi código de cifrado / descifrado y las soluciones proporcionadas no funcionan”

    Primero un descargo de responsabilidad:

    ¡NO use nunca "SecureRandom" para obtener una llave! Esto está roto y no tiene sentido!

    Si está leyendo una clave AES desde el disco, sólo almacene la clave real y no pase por esta extraña danza. Puede obtener un SecretKey para el uso de AES desde los bytes haciendo:

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

    Si está usando 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 la salida de la 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 un rawKey diferente, genuinamente aleatorio cada vez que se ejecuta. Por lo tanto, está intentando descifrar con una clave distinta 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 sólo se habrá guardado la semilla .

    Lo que lo arregló para mí (como @Giorgio sugirió ) estaba reemplazando 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 intentaría solucionar esto: si enfrenta algunos problemas con bouncycastle en la versión de dispositivos / OS, debería abandonar completamente las versiones integradas y en su lugar añadir bouncycastle como jar A su proyecto, cambiar su import para apuntar a ese frasco, reconstruir y asumir que todos los trabajos que serían inmunes a la versión incorporada de Android cambia a partir de ahora.

    Debido a que todo esto no me ayudó a generar una contraseña cifrada que era determinista en todos los dispositivos android (> = 2.1), busqué otra implementación AES. He encontrado uno que funciona para mí en todos los dispositivos. No soy un especialista de seguridad, por lo tanto, por favor, no downvote mi respuesta si la técnica no es tan seguro como podría ser. Sólo estoy publicando el código para las personas que se han ejecutado en el mismo problema que tenía frente 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Í

    Es tiene que ver con la semilla de hecho y es también debe utilizar múltiples de 8 (como 8, 16, 24 o 32), tratar de completar la semilla con A y B o 1 y 0 (tiene que ser algo como este ABAB .. ., Porque AAA .. o BBB .. no funcionará también.) Hasta llegar a un múltiplo de 8 número. Hay otra cosa si estás leyendo y cifrando sólo bytes, (no lo conviertes a Char64 como lo hice), entonces necesitas un PKCS5 o PKCS7 Padding adecuado, sin embargo, en tu caso (debido a sólo 128bits y se ha creado con mayores Versiones de Android) PKCS5 sería suficiente, aunque también debería ponerlo en su SecreteKeySpec algo así como "AES / CBC / PKCS5Padding" o "AES / ECB / PKCS5Padding" en lugar de sólo "AES", ya que Android 4.2 está utilizando PKCS7Padding como predeterminado Y si es sólo bytes que realmente necesita el mismo algoritmo que era el predeterminado antes. Trate de obtener un dispositivo con un Android anterior a 4.2 comprobar el árbol de objetos en su " keygen.init (128, secrand) ", si no me equivoco es tiene la etiqueta de cifrado , que utilizarlo. Darle una oportunidad.

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.