Saltar al contenido

Cómo cifrar cadenas en Java

Solución:

Esta es la primera página que aparece a través de Google y las vulnerabilidades de seguridad en todas las implementaciones me dan vergüenza, así que estoy publicando esto para agregar información sobre el cifrado para otros, como lo ha sido. 7 años de la publicación original. Yo sostengo un Maestría en Ingeniería Informática y pasé mucho tiempo estudiando y aprendiendo criptografía, así que estoy tirando mis dos centavos para hacer de Internet un lugar más seguro.

Además, tenga en cuenta que una gran cantidad de implementación puede ser segura para una situación determinada, pero ¿por qué usarlas y potencialmente cometer un error accidentalmente? Utilice las herramientas más sólidas que tenga disponibles a menos que tenga una razón específica para no hacerlo. En general, recomiendo encarecidamente usar una biblioteca y mantenerse alejado de los detalles esenciales si puede.

ACTUALIZACIÓN 5/4/18: Reescribí algunas partes para que fueran más fáciles de entender y cambié la biblioteca recomendada de Jasypt a la nueva biblioteca de Google Tink, recomendaría eliminar completamente Jasypt de una configuración existente.

Prefacio

Resumiré los conceptos básicos de la criptografía simétrica segura a continuación y señalaré los errores comunes que veo en línea cuando las personas implementan criptografía por su cuenta con la biblioteca estándar de Java. Si desea simplemente omitir todos los detalles, vaya a la nueva biblioteca de Google, Tink, impórtelo en su proyecto y use el modo AES-GCM para todas sus encriptaciones y estará seguro.

Ahora, si desea aprender los detalles esenciales sobre cómo cifrar en Java, siga leyendo 🙂

Cifrados de bloque

Lo primero es lo primero que debe elegir un cifrado de bloque de clave simétrica. Un Block Cipher es una función / programa de computadora utilizado para crear Pseudo-Aleatoriedad. La pseudoaleatoriedad es una falsa aleatoriedad que ninguna otra computadora que no sea una computadora cuántica podría diferenciar entre ella y la aleatoriedad real. El Block Cipher es como el bloque de construcción de la criptografía, y cuando se usa con diferentes modos o esquemas, podemos crear cifrados.

Ahora, con respecto a los algoritmos de cifrado de bloques disponibles hoy, asegúrese de NUNCA, Repito NUNCA use DES, incluso diría que NUNCA use 3DES. El único Block Cipher que incluso el lanzamiento de la NSA de Snowden pudo verificar que es realmente lo más cercano posible a Pseudo-Random es AES 256. También existe AES 128; la diferencia es que AES 256 funciona en bloques de 256 bits, mientras que AES 128 funciona en 128 bloques. Con todo, AES 128 se considera seguro, aunque se han descubierto algunas debilidades, pero 256 es tan sólido como parece.

Dato curioso DES fue roto por la NSA cuando se fundó inicialmente y de hecho lo mantuvo en secreto durante algunos años. Aunque algunas personas todavía afirman que 3DES es seguro, hay bastantes trabajos de investigación que han encontrado y analizado debilidades en 3DES.

Modos de cifrado

El cifrado se crea cuando se toma un cifrado en bloque y se usa un esquema específico para que la aleatoriedad se combine con una clave para crear algo que sea reversible siempre que conozca la clave. Esto se conoce como modo de encriptación.

A continuación, se muestra un ejemplo de un modo de cifrado y el modo más simple conocido como ECB para que pueda comprender visualmente lo que está sucediendo:

Modo ECB

Los modos de cifrado que verá con más frecuencia en línea son los siguientes:

ECB CTR, CBC, GCM

Existen otros modos fuera de los enumerados y los investigadores siempre están trabajando hacia nuevos modos para mejorar los problemas existentes.

Ahora pasemos a las implementaciones y lo que es seguro. NUNCA use ECB, esto es malo para ocultar datos repetidos como lo muestra el famoso pingüino de Linux.Ejemplo de pingüino de Linux

Al implementar en Java, tenga en cuenta que si usa el siguiente código, el modo ECB está configurado de forma predeterminada:

Cipher cipher = Cipher.getInstance("AES");

… ¡PELIGRO ESTA ES UNA VULNERABILIDAD! y, desafortunadamente, esto se ve en todo StackOverflow y en línea en tutoriales y ejemplos.

Nonces y IVs

En respuesta al problema encontrado con el modo ECB, se crearon sustantivos también conocidos como IV. La idea es que generemos una nueva variable aleatoria y la adjuntemos a cada encriptación para que cuando encriptes dos mensajes que son iguales salgan diferentes. La belleza detrás de esto es que un IV o nonce es de conocimiento público. Eso significa que un atacante puede tener acceso a esto, pero mientras no tenga su clave, no puede hacer nada con ese conocimiento.

Los problemas comunes que veré es que las personas establecerán el IV como un valor estático como en el mismo valor fijo en su código. y aquí está el peligro de los IVs, en el momento en que repite uno, compromete toda la seguridad de su cifrado.

Generando un IV aleatorio

SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
byte[] iv = new byte[cipher.getBlockSize()];
randomSecureRandom.nextBytes(iv);
IvParameterSpec ivParams = new IvParameterSpec(iv);

Nota: SHA1 está roto, pero no pude encontrar cómo implementar SHA256 en este caso de uso correctamente, por lo que si alguien quiere intentarlo y actualizarlo, ¡sería increíble! Además, los ataques SHA1 siguen siendo poco convencionales, ya que en un clúster enorme puede llevar algunos años descifrarse. Consulte los detalles aquí.

Implementación de CTR

No se requiere relleno para el modo CTR.

 Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");

Implementación de CBC

Si elige implementar el modo CBC, hágalo con PKCS7Padding de la siguiente manera:

 Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");

Vulnerabilidad de CBC y CTR y por qué debería usar GCM

Aunque algunos otros modos, como CBC y CTR, son seguros, se encuentran con el problema de que un atacante puede voltear los datos cifrados, cambiando su valor cuando se descifran. Así que digamos que encripta un mensaje bancario imaginario “Vender 100”, su mensaje encriptado se ve así “eu23ng”, el atacante cambia un bit a “eu53ng” y de repente cuando descifra su mensaje, se lee como “Vender 900”.

Para evitar esto, la mayoría de Internet usa GCM, y cada vez que ve HTTPS probablemente estén usando GCM. GCM firma el mensaje cifrado con un hash y comprueba que el mensaje no se haya modificado con esta firma.

Evitaría implementar GCM debido a su complejidad. Es mejor que uses la nueva biblioteca de Google Tink porque, de nuevo, si repites accidentalmente un IV, estás comprometiendo la clave en el caso de GCM, que es la falla de seguridad máxima. Nuevos investigadores están trabajando hacia modos de cifrado resistentes a la repetición IV donde incluso si repite el IV, la clave no está en peligro, pero esto aún no se ha generalizado.

Ahora, si desea implementar GCM, aquí hay un enlace a una buena implementación de GCM. Sin embargo, no puedo garantizar la seguridad o si se implementa correctamente, pero tiene las bases. También tenga en cuenta que con GCM no hay relleno.

Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

Claves vs contraseñas

Otra nota muy importante es que cuando se trata de criptografía, una clave y una contraseña no son lo mismo. Una clave en criptografía debe tener una cierta cantidad de entropía y aleatoriedad para ser considerada segura. Es por eso que debe asegurarse de utilizar las bibliotecas criptográficas adecuadas para generar la clave.

Entonces, realmente tiene dos implementaciones que puede hacer aquí, la primera es usar el código que se encuentra en este hilo de StackOverflow para la generación de claves aleatorias. Esta solución utiliza un generador de números aleatorios seguro para crear una clave desde cero que puede usar.

La otra opción menos segura es utilizar la entrada del usuario, como una contraseña. El problema que comentamos es que la contraseña no tiene suficiente entropía, por lo que tendríamos que usar PBKDF2, un algoritmo que toma la contraseña y la fortalece. Aquí hay una implementación de StackOverflow que me gustó. Sin embargo, la biblioteca de Google Tink tiene todo esto integrado y debes aprovecharlo.

Desarrolladores de Android

Un punto importante para señalar aquí es saber que su código de Android es de ingeniería inversa y la mayoría de los casos, la mayoría de los códigos de Java también lo son. Eso significa que si almacena la contraseña en texto sin formato en su código. Un pirata informático puede recuperarlo fácilmente. Por lo general, para este tipo de cifrado, desea utilizar criptografía asimétrica, etc. Esto está fuera del alcance de esta publicación, por lo que evitaré sumergirme en él.

Una lectura interesante de 2013: señala que el 88% de las implementaciones de Crypto en Android se realizaron de manera incorrecta.

Pensamientos finales

Una vez más, sugeriría evitar implementar la biblioteca java para criptografía directamente y usar Google Tink, le ahorrará el dolor de cabeza, ya que realmente han hecho un buen trabajo implementando todos los algoritmos correctamente. E incluso entonces asegúrese de verificar los problemas que surgen en el github de Tink, las vulnerabilidades emergentes aquí y allá.

Si tiene alguna pregunta o comentario, ¡no dude en comentarlo! La seguridad siempre cambia y debe hacer todo lo posible para mantenerse al día 🙂

Recomendaría usar algún cifrado simétrico estándar que esté ampliamente disponible como DES, 3DES o AES. Si bien ese no es el algoritmo más seguro, hay muchas implementaciones y solo necesitaría darle la clave a cualquiera que se suponga que descifre la información en el código de barras. javax.crypto.Cipher es con lo que quiere trabajar aquí.

Supongamos que los bytes para cifrar están en

byte[] input;

A continuación, necesitará la clave y los bytes del vector de inicialización.

byte[] keyBytes;
byte[] ivBytes;

Ahora puede inicializar el cifrado para el algoritmo que seleccione:

// wrap key data in Key/IV specs to pass to cipher
SecretKeySpec key = new SecretKeySpec(keyBytes, "DES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// create the cipher with the algorithm you choose
// see javadoc for Cipher class for more info, e.g.
Cipher cipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

El cifrado sería así:

cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] encrypted= new byte[cipher.getOutputSize(input.length)];
int enc_len = cipher.update(input, 0, input.length, encrypted, 0);
enc_len += cipher.doFinal(encrypted, enc_len);

Y descifrado como este:

cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
byte[] decrypted = new byte[cipher.getOutputSize(enc_len)];
int dec_len = cipher.update(encrypted, 0, enc_len, decrypted, 0);
dec_len += cipher.doFinal(decrypted, dec_len);

Advertencia

No use esto como algún tipo de medida de seguridad.

El mecanismo de cifrado en esta publicación es un bloc de un solo uso, lo que significa que un atacante puede recuperar fácilmente la clave secreta utilizando 2 mensajes cifrados. Mensajes encriptados XOR 2 y obtienes la clave. ¡Así de simple!

Señalado por Moussa


Estoy usando Base64Encoder / Decoder de Sun, que se encuentra en JRE de Sun, para evitar otro JAR en lib. Eso es peligroso desde el punto de vista de usar OpenJDK o el JRE de algún otro. Además de eso, ¿hay otra razón por la que debería considerar usar Apache commons lib con Encoder / Decoder?

public class EncryptUtils {
    public static final String DEFAULT_ENCODING = "UTF-8"; 
    static BASE64Encoder enc = new BASE64Encoder();
    static BASE64Decoder dec = new BASE64Decoder();

    public static String base64encode(String text) {
        try {
            return enc.encode(text.getBytes(DEFAULT_ENCODING));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }//base64encode

    public static String base64decode(String text) {
        try {
            return new String(dec.decodeBuffer(text), DEFAULT_ENCODING);
        } catch (IOException e) {
            return null;
        }
    }//base64decode

    public static void main(String[] args) {
        String txt = "some text to be encrypted";
        String key = "key phrase used for XOR-ing";
        System.out.println(txt + " XOR-ed to: " + (txt = xorMessage(txt, key)));

        String encoded = base64encode(txt);       
        System.out.println(" is encoded to: " + encoded + " and that is decoding to: " + (txt = base64decode(encoded)));
        System.out.print("XOR-ing back to original: " + xorMessage(txt, key));
    }

    public static String xorMessage(String message, String key) {
        try {
            if (message == null || key == null) return null;

            char[] keys = key.toCharArray();
            char[] mesg = message.toCharArray();

            int ml = mesg.length;
            int kl = keys.length;
            char[] newmsg = new char[ml];

            for (int i = 0; i < ml; i++) {
                newmsg[i] = (char)(mesg[i] ^ keys[i % kl]);
            }//for i

            return new String(newmsg);
        } catch (Exception e) {
            return null;
        }
    }//xorMessage
}//class
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *