¿Cuáles son las mejores prácticas para usar el cifrado AES en Android?

90

Por qué hago esta pregunta:

Sé que ha habido muchas preguntas sobre el cifrado AES, incluso para Android. Y hay muchos fragmentos de código si busca en la Web. Pero en cada página, en cada pregunta de Stack Overflow, encuentro otra implementación con grandes diferencias.

Así que creé esta pregunta para encontrar una "mejor práctica". ¡Espero que podamos recopilar una lista de los requisitos más importantes y configurar una implementación que sea realmente segura!

Leí sobre vectores de inicialización y sales. No todas las implementaciones que encontré tenían estas características. Entonces, ¿lo necesitas? ¿Aumenta mucho la seguridad? ¿Cómo lo implementas? ¿Debería el algoritmo generar excepciones si los datos cifrados no se pueden descifrar? ¿O es eso inseguro y debería devolver una cadena ilegible? ¿Puede el algoritmo usar Bcrypt en lugar de SHA?

¿Qué pasa con estas dos implementaciones que encontré? Estan bien ¿Perfecto o faltan algunas cosas importantes? ¿Qué de estos es seguro?

El algoritmo debe tomar una cadena y una "contraseña" para el cifrado y luego cifrar la cadena con esa contraseña. La salida debería ser una cadena (¿hexadecimal o base64?) Nuevamente. El descifrado también debería ser posible, por supuesto.

¿Cuál es la implementación AES perfecta para Android?

Implementación # 1:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class AdvancedCrypto implements ICrypto {

        public static final String PROVIDER = "BC";
        public static final int SALT_LENGTH = 20;
        public static final int IV_LENGTH = 16;
        public static final int PBE_ITERATION_COUNT = 100;

        private static final String RANDOM_ALGORITHM = "SHA1PRNG";
        private static final String HASH_ALGORITHM = "SHA-512";
        private static final String PBE_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
        private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
        private static final String SECRET_KEY_ALGORITHM = "AES";

        public String encrypt(SecretKey secret, String cleartext) throws CryptoException {
                try {

                        byte[] iv = generateIv();
                        String ivHex = HexEncoder.toHex(iv);
                        IvParameterSpec ivspec = new IvParameterSpec(iv);

                        Cipher encryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        encryptionCipher.init(Cipher.ENCRYPT_MODE, secret, ivspec);
                        byte[] encryptedText = encryptionCipher.doFinal(cleartext.getBytes("UTF-8"));
                        String encryptedHex = HexEncoder.toHex(encryptedText);

                        return ivHex + encryptedHex;

                } catch (Exception e) {
                        throw new CryptoException("Unable to encrypt", e);
                }
        }

        public String decrypt(SecretKey secret, String encrypted) throws CryptoException {
                try {
                        Cipher decryptionCipher = Cipher.getInstance(CIPHER_ALGORITHM, PROVIDER);
                        String ivHex = encrypted.substring(0, IV_LENGTH * 2);
                        String encryptedHex = encrypted.substring(IV_LENGTH * 2);
                        IvParameterSpec ivspec = new IvParameterSpec(HexEncoder.toByte(ivHex));
                        decryptionCipher.init(Cipher.DECRYPT_MODE, secret, ivspec);
                        byte[] decryptedText = decryptionCipher.doFinal(HexEncoder.toByte(encryptedHex));
                        String decrypted = new String(decryptedText, "UTF-8");
                        return decrypted;
                } catch (Exception e) {
                        throw new CryptoException("Unable to decrypt", e);
                }
        }

        public SecretKey getSecretKey(String password, String salt) throws CryptoException {
                try {
                        PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), HexEncoder.toByte(salt), PBE_ITERATION_COUNT, 256);
                        SecretKeyFactory factory = SecretKeyFactory.getInstance(PBE_ALGORITHM, PROVIDER);
                        SecretKey tmp = factory.generateSecret(pbeKeySpec);
                        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), SECRET_KEY_ALGORITHM);
                        return secret;
                } catch (Exception e) {
                        throw new CryptoException("Unable to get secret key", e);
                }
        }

        public String getHash(String password, String salt) throws CryptoException {
                try {
                        String input = password + salt;
                        MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM, PROVIDER);
                        byte[] out = md.digest(input.getBytes("UTF-8"));
                        return HexEncoder.toHex(out);
                } catch (Exception e) {
                        throw new CryptoException("Unable to get hash", e);
                }
        }

        public String generateSalt() throws CryptoException {
                try {
                        SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                        byte[] salt = new byte[SALT_LENGTH];
                        random.nextBytes(salt);
                        String saltHex = HexEncoder.toHex(salt);
                        return saltHex;
                } catch (Exception e) {
                        throw new CryptoException("Unable to generate salt", e);
                }
        }

        private byte[] generateIv() throws NoSuchAlgorithmException, NoSuchProviderException {
                SecureRandom random = SecureRandom.getInstance(RANDOM_ALGORITHM);
                byte[] iv = new byte[IV_LENGTH];
                random.nextBytes(iv);
                return iv;
        }

}

Fuente: http://pocket-for-android.1047292.n5.nabble.com/Encryption-method-and-reading-the-Dropbox-backup-td4344194.html

Implementación # 2:

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

/**
 * Usage:
 * <pre>
 * String crypto = SimpleCrypto.encrypt(masterpassword, cleartext)
 * ...
 * String cleartext = SimpleCrypto.decrypt(masterpassword, crypto)
 * </pre>
 * @author ferenc.hechler
 */
public class SimpleCrypto {

    public static String encrypt(String seed, String cleartext) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] result = encrypt(rawKey, cleartext.getBytes());
        return toHex(result);
    }

    public static String decrypt(String seed, String encrypted) throws Exception {
        byte[] rawKey = getRawKey(seed.getBytes());
        byte[] enc = toByte(encrypted);
        byte[] result = decrypt(rawKey, enc);
        return new String(result);
    }

    private static byte[] getRawKey(byte[] seed) throws Exception {
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
        sr.setSeed(seed);
        kgen.init(128, sr); // 192 and 256 bits may not be available
        SecretKey skey = kgen.generateKey();
        byte[] raw = skey.getEncoded();
        return raw;
    }


    private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
        byte[] encrypted = cipher.doFinal(clear);
        return encrypted;
    }

    private static byte[] decrypt(byte[] raw, byte[] encrypted) throws Exception {
        SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec);
        byte[] decrypted = cipher.doFinal(encrypted);
        return decrypted;
    }

    public static String toHex(String txt) {
        return toHex(txt.getBytes());
    }
    public static String fromHex(String hex) {
        return new String(toByte(hex));
    }

    public static byte[] toByte(String hexString) {
        int len = hexString.length()/2;
        byte[] result = new byte[len];
        for (int i = 0; i < len; i++)
            result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
        return result;
    }

    public static String toHex(byte[] buf) {
        if (buf == null)
            return "";
        StringBuffer result = new StringBuffer(2*buf.length);
        for (int i = 0; i < buf.length; i++) {
            appendHex(result, buf[i]);
        }
        return result.toString();
    }
    private final static String HEX = "0123456789ABCDEF";
    private static void appendHex(StringBuffer sb, byte b) {
        sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
    }

}

Fuente: http://www.tutorials-android.com/learn/How_to_encrypt_and_decrypt_strings.rhtml

graznar
fuente
Estoy tratando de implementar la solución 1 pero necesitaba algunas clases. ¿tienes el código fuente completo?
Albanx
1
No, no lo he hecho, lo siento. Pero lo hice funcionar simplemente borrando implements ICryptoy cambiando throws CryptoExceptiona throws Exceptiony así sucesivamente. Entonces ya no necesitarás esas clases.
caw
¿Pero también falta la clase HexEncoder? ¿Dónde puedo encontrarlo?
Albanx
HexEncoder es parte de la biblioteca BouncyCastle, creo. Puede descargarlo. O puede buscar en Google "byte [] a hexadecimal" y viceversa en Java.
caw
Gracias Marco. Pero noto que hay 3 métodos getSecretKey, getHash, generateSalten la primera aplicación que no han sido utilizados. Tal vez esté equivocado, pero ¿cómo podría usarse esta clase para cifrar una cadena en la práctica?
Albanx

Respuestas:

37

Ninguna de las implementaciones que proporcione en su pregunta es del todo correcta, y ninguna de las implementaciones que proporcione debe usarse tal cual. A continuación, discutiré aspectos del cifrado basado en contraseña en Android.

Claves y hashes

Comenzaré a discutir el sistema basado en contraseñas con sales. La sal es un número generado aleatoriamente. No se "deduce". La implementación 1 incluye un generateSalt()método que genera un número aleatorio criptográficamente fuerte. Debido a que la sal es importante para la seguridad, debe mantenerse en secreto una vez que se genera, aunque solo debe generarse una vez. Si se trata de un sitio web, es relativamente fácil mantener el secreto de la sal, pero para las aplicaciones instaladas (para escritorio y dispositivos móviles), esto será mucho más difícil.

El método getHash()devuelve un hash de la contraseña y la sal dadas, concatenados en una sola cadena. El algoritmo utilizado es SHA-512, que devuelve un hash de 512 bits. Este método devuelve un hash que es útil para verificar la integridad de una cadena, por lo que también podría usarse llamando getHash()con solo una contraseña o solo una sal, ya que simplemente concatena ambos parámetros. Dado que este método no se utilizará en el sistema de cifrado basado en contraseña, no lo discutiré más.

El método getSecretKey()deriva una clave de una charmatriz de la contraseña y un salt codificado en hexadecimal, como se devuelve de generateSalt(). El algoritmo utilizado es PBKDF1 (creo) de PKCS5 con SHA-256 como función hash, y devuelve una clave de 256 bits. getSecretKey()genera una clave generando repetidamente hashes de la contraseña, sal y un contador (hasta el recuento de iteraciones indicado PBE_ITERATION_COUNT, aquí 100) para aumentar el tiempo necesario para montar un ataque de fuerza bruta. La longitud de la sal debe ser al menos tan larga como la clave que se genera, en este caso, al menos 256 bits. El recuento de iteraciones debe establecerse el mayor tiempo posible sin causar un retraso excesivo. Para obtener más información sobre las sales y los recuentos de iteraciones en la derivación de claves, consulte la sección 4 en RFC2898 .

La implementación en el PBE de Java, sin embargo, es defectuosa si la contraseña contiene caracteres Unicode, es decir, aquellos que requieren más de 8 bits para ser representados. Como se indica en PBEKeySpec, "el mecanismo PBE definido en PKCS # 5 mira solo los 8 bits de orden inferior de cada carácter". Para solucionar este problema, puede intentar generar una cadena hexadecimal (que contendrá solo caracteres de 8 bits) de todos los caracteres de 16 bits en la contraseña antes de pasarla a PBEKeySpec. Por ejemplo, "ABC" se convierte en "004100420043". Tenga en cuenta también que PBEKeySpec "solicita la contraseña como una matriz de caracteres, por lo que se puede sobrescribir [con clearPassword()] cuando termine". (Con respecto a "proteger cadenas en la memoria", consulte esta pregunta ). Sin embargo, no veo ningún problema,

Cifrado

Una vez que se genera una clave, podemos usarla para cifrar y descifrar texto.

En la implementación 1, el algoritmo de cifrado utilizado es AES/CBC/PKCS5Padding, es decir, AES en el modo de cifrado Cipher Block Chaining (CBC), con relleno definido en PKCS # 5. (Otros modos de cifrado AES incluyen el modo de contador (CTR), el modo de libro de códigos electrónico (ECB) y el modo de contador de Galois (GCM). Otra pregunta sobre Stack Overflow contiene respuestas que discuten en detalle los diversos modos de cifrado AES y los recomendados para usar. Tenga en cuenta también que hay varios ataques al cifrado en modo CBC, algunos de los cuales se mencionan en RFC 7457.)

Tenga en cuenta que debe utilizar un modo de cifrado que también verifique la integridad de los datos cifrados (por ejemplo, cifrado autenticado con datos asociados , AEAD, descrito en RFC 5116). Sin embargo, AES/CBC/PKCS5Paddingno proporciona verificación de integridad, por lo que no se recomienda solo . Para propósitos de AEAD, se recomienda usar un secreto que sea al menos dos veces más largo que una clave de cifrado normal, para evitar ataques de clave relacionados: la primera mitad sirve como clave de cifrado y la segunda mitad sirve como clave para la verificación de integridad. (Es decir, en este caso, generar un único secreto a partir de una contraseña y sal, y dividir ese secreto en dos).

Implementación de Java

Las diversas funciones de la implementación 1 utilizan un proveedor específico, a saber, "BC", para sus algoritmos. Sin embargo, en general, no se recomienda solicitar proveedores específicos, ya que no todos los proveedores están disponibles en todas las implementaciones de Java, ya sea por falta de soporte, para evitar la duplicación de código o por otras razones. Este consejo se ha vuelto especialmente importante desde el lanzamiento de la vista previa de Android P a principios de 2018, porque algunas funciones del proveedor "BC" han quedado obsoletas allí; consulte el artículo "Cambios en criptografía en Android P" en el Blog de desarrolladores de Android. Consulte también la Introducción a los proveedores de Oracle .

Por lo tanto, PROVIDERno debería existir y la cadena -BCdebería eliminarse PBE_ALGORITHM. La implementación 2 es correcta a este respecto.

No es apropiado que un método capture todas las excepciones, sino que maneje solo las excepciones que pueda. Las implementaciones dadas en su pregunta pueden generar una variedad de excepciones comprobadas. Un método puede optar por envolver solo las excepciones marcadas con CryptoException, o especificar esas excepciones marcadas en la throwscláusula. Por conveniencia, encapsular la excepción original con CryptoException puede ser apropiado aquí, ya que hay potencialmente muchas excepciones marcadas que las clases pueden lanzar.

SecureRandom en Android

Como se detalla en el artículo "Some SecureRandom Thoughts", en el Blog de desarrolladores de Android, la implementación de java.security.SecureRandomversiones de Android antes de 2013 tiene una falla que reduce la fuerza de los números aleatorios que ofrece. Esta falla se puede mitigar como se describe en ese artículo.

Peter O.
fuente
Esa generación de doble secreto es un poco derrochador en mi opinión, podría dividir fácilmente el secreto generado en dos o, si no hay suficientes bits disponibles, agregar un contador (1 para la primera clave, 2 para la segunda clave) a la secreto y realizar un solo hash. No es necesario realizar todas las iteraciones dos veces.
Maarten Bodewes
Gracias por la información sobre HMAC y la sal. No usaré HMAC esta vez, pero luego podría ser muy útil. Y, en general, esto es algo bueno, sin duda.
caw
¡Muchas gracias por todas las ediciones y esta (ahora) maravillosa introducción al cifrado AES en Java!
caw
1
Debería. getInstancetiene una sobrecarga que toma solo el nombre del algoritmo. Ejemplo: Cipher.getInstance () Varios proveedores, incluido Bouncy Castle, pueden estar registrados en la implementación de Java y este tipo de sobrecarga busca en la lista de proveedores uno de ellos que implemente el algoritmo dado. Deberías probarlo y verás.
Peter O.2 de
1
Sí, buscará los proveedores en el orden dado por Security.getProviders (), aunque ahora también verificará si la clave es aceptada por ese proveedor durante la llamada init () permitiendo el cifrado asistido por hardware. Más detalles aquí: docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/… .
Maarten Bodewes
18

# 2 nunca debe usarse ya que solo usa "AES" (lo que significa cifrado en modo ECB en texto, un gran no-no) para el cifrado. Solo hablaré del # 1.

La primera implementación parece adherirse a las mejores prácticas para el cifrado. Las constantes son generalmente correctas, aunque tanto el tamaño de la sal como el número de iteraciones para realizar PBE están en el lado corto. Además, parece ser para AES-256 ya que la generación de claves PBE usa 256 como un valor codificado (una pena después de todas esas constantes). Utiliza CBC y PKCS5Padding, que es al menos lo que cabría esperar.

Falta por completo cualquier protección de autenticación / integridad, por lo que un atacante puede cambiar el texto cifrado. Esto significa que los ataques de relleno de Oracle son posibles en un modelo cliente / servidor. También significa que un atacante puede intentar cambiar los datos cifrados. Esto probablemente dará como resultado algún error en alguna parte debido a que la aplicación no acepta el relleno o el contenido, pero esa no es una situación en la que desea estar.

El manejo de excepciones y la validación de entrada podrían mejorarse, la captura de excepciones siempre es incorrecta en mi libro. Además, la clase implementa ICrypt, que no sé. Sé que tener solo métodos sin efectos secundarios en una clase es un poco extraño. Normalmente, los haría estáticos. No hay almacenamiento en búfer de instancias de Cipher, etc., por lo que cada objeto requerido se crea ad-nauseum. Sin embargo, puede eliminar de forma segura ICrypto de la definición que parece, en ese caso también podría refactorizar el código a métodos estáticos (o reescribirlo para que esté más orientado a objetos, su elección).

El problema es que cualquier contenedor siempre hace suposiciones sobre el caso de uso. Decir que un envoltorio está bien o mal es, por tanto, una tontería. Es por eso que siempre trato de evitar generar clases contenedoras. Pero al menos no parece explícitamente incorrecto.

Maarten Bodewes
fuente
¡Muchas gracias por esta respuesta detallada! Sé que es una pena, pero aún no conocía la sección de revisión de código: D Gracias por esta pista, lo comprobaré. Pero esta pregunta también encaja aquí en mi opinión, ya que no solo quiero una revisión de estos fragmentos de código. En cambio, quiero preguntarles qué aspectos son importantes al implementar el cifrado AES en Android. Y tiene razón nuevamente, este fragmento de código es para AES-256. Entonces, ¿diría que esta es una implementación general segura de AES-256? El caso de uso es que solo quiero almacenar de forma segura información de texto en una base de datos.
caw
1
Se ve bien, pero la idea de no tener controles de integridad y autenticación me molestaría. Si tiene suficiente espacio, consideraría seriamente agregar un HMAC sobre el texto cifrado. Dicho esto, como probablemente esté intentando simplemente agregar confidencialidad, lo consideraría una gran ventaja, pero no directamente un requisito.
Maarten Bodewes
Pero si la intención es solo que otros no tengan acceso a la información encriptada, no necesito un HMAC, ¿verdad? Si cambian el texto cifrado y fuerzan un resultado "incorrecto" de descifrado, no hay ningún problema real, ¿verdad?
caw
Si eso no está en su escenario de riesgo, entonces está bien. Si de alguna manera pueden desencadenar un descifrado repetido por parte del sistema después de alterar el texto cifrado (un ataque de oráculo de relleno), entonces podrían descifrar los datos sin conocer la clave. No pueden hacer esto si simplemente obtienen los datos en un sistema que no tiene la clave. Pero es por eso que siempre es una buena práctica agregar un HMAC. Personalmente, consideraría un sistema con AES-128 y HMAC más seguro que AES-256 sin, pero como se dijo, probablemente no sea necesario.
Maarten Bodewes
1
¿Por qué no utilizar AES en modo Galois / Counter (AES-GCM) si desea integridad?
Kimvais
1

Has hecho una pregunta bastante interesante. Al igual que con todos los algoritmos, la clave de cifrado es la "salsa secreta", ya que una vez que el público lo conoce, todo lo demás también lo es. Así que busca formas de acceder a este documento de Google

seguridad

Además de la facturación integrada en la aplicación de Google, también ofrece ideas sobre seguridad, lo que también es revelador

billing_best_practices

the100rabh
fuente
¡Gracias por estos enlaces! ¿Qué quiere decir exactamente con "cuando la clave de cifrado está fuera, todo lo demás también"?
caw
Lo que quiero decir es que la clave de cifrado debe ser segura, si alguien puede conseguirlo, entonces sus datos cifrados son tan buenos como el texto sin formato. Por favor, voten si mi respuesta le resultó útil hasta cierto punto :-)
the100rabh
0

Utilice la API ligera de BouncyCastle. Proporciona 256 AES con PBE y Salt.
Aquí se muestra un código que puede cifrar / descifrar archivos.

public void encrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(true, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(true, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }

}

public void decrypt(InputStream fin, OutputStream fout, String password) {
    try {
        PKCS12ParametersGenerator pGen = new PKCS12ParametersGenerator(new SHA256Digest());
        char[] passwordChars = password.toCharArray();
        final byte[] pkcs12PasswordBytes = PBEParametersGenerator.PKCS12PasswordToBytes(passwordChars);
        pGen.init(pkcs12PasswordBytes, salt.getBytes(), iterationCount);
        CBCBlockCipher aesCBC = new CBCBlockCipher(new AESEngine());
        ParametersWithIV aesCBCParams = (ParametersWithIV) pGen.generateDerivedParameters(256, 128);
        aesCBC.init(false, aesCBCParams);
        PaddedBufferedBlockCipher aesCipher = new PaddedBufferedBlockCipher(aesCBC, new PKCS7Padding());
        aesCipher.init(false, aesCBCParams);

        // Read in the decrypted bytes and write the cleartext to out
        int numRead = 0;
        while ((numRead = fin.read(buf)) >= 0) {
            if (numRead == 1024) {
                byte[] plainTemp = new byte[aesCipher.getUpdateOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                // int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            } else {
                byte[] plainTemp = new byte[aesCipher.getOutputSize(numRead)];
                int offset = aesCipher.processBytes(buf, 0, numRead, plainTemp, 0);
                int last = aesCipher.doFinal(plainTemp, offset);
                final byte[] plain = new byte[offset + last];
                System.arraycopy(plainTemp, 0, plain, 0, plain.length);
                fout.write(plain, 0, plain.length);
            }
        }
        fout.close();
        fin.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
Kelheor
fuente
¡Gracias! Esta es probablemente una solución buena y segura, pero no quiero utilizar software de terceros. Estoy seguro de que debe ser posible implementar AES de forma segura por uno mismo.
caw
2
Depende de si desea incluir protección contra ataques de canal lateral. En general, debe asumir que no es seguro implementar algoritmos criptográficos por su cuenta. Como AES CBC está disponible en las bibliotecas de tiempo de ejecución de Java de Oracle, probablemente sea mejor usarlas y usar las bibliotecas de Bouncy Castle si un algoritmo no está disponible.
Maarten Bodewes
Falta la definición de buf( realmente espero que no sea un staticcampo). También se parece a ambos encrypt()y decrypt()no procesará correctamente el bloque final si la entrada es un múltiplo de 1024 bytes.
tc.