Bytes iniciales incorrectos después del descifrado de Java AES / CBC

116

¿Qué pasa con el siguiente ejemplo?

El problema es que la primera parte de la cadena descifrada no tiene sentido. Sin embargo, el resto está bien, me sale ...

Result: eB6OgeS��i are you? Have a nice day.
@Test
public void testEncrypt() {
  try {
    String s = "Hello there. How are you? Have a nice day.";

    // Generate key
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
    SecretKey aesKey = kgen.generateKey();

    // Encrypt cipher
    Cipher encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    encryptCipher.init(Cipher.ENCRYPT_MODE, aesKey);

    // Encrypt
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, encryptCipher);
    cipherOutputStream.write(s.getBytes());
    cipherOutputStream.flush();
    cipherOutputStream.close();
    byte[] encryptedBytes = outputStream.toByteArray();

    // Decrypt cipher
    Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
    decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

    // Decrypt
    outputStream = new ByteArrayOutputStream();
    ByteArrayInputStream inStream = new ByteArrayInputStream(encryptedBytes);
    CipherInputStream cipherInputStream = new CipherInputStream(inStream, decryptCipher);
    byte[] buf = new byte[1024];
    int bytesRead;
    while ((bytesRead = cipherInputStream.read(buf)) >= 0) {
        outputStream.write(buf, 0, bytesRead);
    }

    System.out.println("Result: " + new String(outputStream.toByteArray()));

  } 
  catch (Exception ex) {
    ex.printStackTrace();
  }
}
TedTrippin
fuente
48
¡NO USE NINGUNA RESPUESTA A ESTA PREGUNTA EN UN PROYECTO SERIO! Todos los ejemplos proporcionados en esta pregunta son vulnerables al relleno de Oracle y son, en general, un uso de criptografía muy malo. Introducirá una vulnerabilidad de criptografía grave en su proyecto utilizando cualquiera de los fragmentos a continuación.
HoLyVieR
16
@HoLyVieR, con respecto a las siguientes citas: "No debería desarrollar su propia biblioteca de criptografía" y "utilizar una API de alto nivel que proporciona su marco". Nadie aquí está desarrollando su propia biblioteca de criptografía. Simplemente estamos utilizando la API de alto nivel ya existente que proporciona el marco de Java. Usted señor es tremendamente inexacto.
k170
10
@MaartenBodewes, Solo porque ambos estén de acuerdo no implica que ambos estén en lo correcto. Los buenos desarrolladores conocen la diferencia entre empaquetar una API de alto nivel y reescribir una API de bajo nivel. Los buenos lectores notarán que el OP pidió un "ejemplo simple de cifrado / descifrado AES de Java" y eso es exactamente lo que obtuvo . Tampoco estoy de acuerdo con las otras respuestas, por lo que publiqué una respuesta propia. Quizás ustedes deberían intentar lo mismo e iluminarnos a todos con su experiencia.
k170
6
@HoLyVieR ¡Eso es realmente lo más absurdo que he leído en SO! ¿Quién eres tú para decirle a la gente lo que pueden y no pueden desarrollar?
TedTrippin
14
Todavía no veo ejemplos @HoLyVieR. Veamos algunos, ¿o punteros a bibliotecas? Nada constructivo.
danieljimenez

Respuestas:

245

Muchas personas, incluyéndome a mí, enfrentan muchos problemas al hacer que esto funcione debido a que faltan información como, olvidar convertir a Base64, vectores de inicialización, juego de caracteres, etc. Así que pensé en hacer un código completamente funcional.

Espero que esto sea útil para todos: Para compilar, necesita un jar adicional de Apache Commons Codec, que está disponible aquí: http://commons.apache.org/proper/commons-codec/download_codec.cgi

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string: "
                    + Base64.encodeBase64String(encrypted));

            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(decrypt(key, initVector,
                encrypt(key, initVector, "Hello World")));
    }
}
Chand Priyankara
fuente
47
Si no desea depender de la biblioteca de códecs Apache Commons de terceros, puede usar javax.xml.bind.DatatypeConverter de JDK para realizar la codificación / decodificación Base64: System.out.println("encrypted string:" + DatatypeConverter.printBase64Binary(encrypted)); byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted));
curd0
8
¿Estás usando una IV constante?
vianna77
36
Java 8 ya tiene herramientas Base64: java.util.Base64.getDecoder () y java.util.Base64.getEncoder ()
Hristo Stoyanov
11
El IV no tiene que ser secreto, pero tiene que ser impredecible para el modo CBC (y único para CTR). Puede enviarse junto con el texto cifrado. Una forma común de hacer esto es prefijar el IV al texto cifrado y cortarlo antes de descifrarlo. Debe generarse a través deSecureRandom
Artjom B.
6
Una contraseña no es una clave. Una vía intravenosa debe ser aleatoria.
Maarten Bodewes
40

Aquí una solución sin Apache Commons Codec's Base64:

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

public class AdvancedEncryptionStandard
{
    private byte[] key;

    private static final String ALGORITHM = "AES";

    public AdvancedEncryptionStandard(byte[] key)
    {
        this.key = key;
    }

    /**
     * Encrypts the given plain text
     *
     * @param plainText The plain text to encrypt
     */
    public byte[] encrypt(byte[] plainText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        return cipher.doFinal(plainText);
    }

    /**
     * Decrypts the given byte array
     *
     * @param cipherText The data to decrypt
     */
    public byte[] decrypt(byte[] cipherText) throws Exception
    {
        SecretKeySpec secretKey = new SecretKeySpec(key, ALGORITHM);
        Cipher cipher = Cipher.getInstance(ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        return cipher.doFinal(cipherText);
    }
}

Ejemplo de uso:

byte[] encryptionKey = "MZygpewJsCpRrfOr".getBytes(StandardCharsets.UTF_8);
byte[] plainText = "Hello world!".getBytes(StandardCharsets.UTF_8);
AdvancedEncryptionStandard advancedEncryptionStandard = new AdvancedEncryptionStandard(
        encryptionKey);
byte[] cipherText = advancedEncryptionStandard.encrypt(plainText);
byte[] decryptedCipherText = advancedEncryptionStandard.decrypt(cipherText);

System.out.println(new String(plainText));
System.out.println(new String(cipherText));
System.out.println(new String(decryptedCipherText));

Huellas dactilares:

Hello world!
դ;��LA+�ߙb*
Hello world!
BullyWiiPlaza
fuente
5
Este es un ejemplo perfectamente funcional, como el de @chandpriyankara. Pero, ¿por qué definir una firma de encrypt(String)y no encrypt(byte[] )?. El cifrado (también el descifrado) es un proceso basado en bytes (de todos modos, AES lo es). El cifrado toma bytes como entrada y genera bytes, al igual que el descifrado (ejemplo: el Cipherobjeto lo hace). Ahora, un caso de uso particular puede ser tener bytes encriptados provenientes de una Cadena, o ser enviados como una Cadena (adjunto MIME base64 para un Correo ...), pero ese es un problema de codificación de bytes, para los cuales existen cientos de soluciones, totalmente ajenas a AES / cifrado.
GPI
3
@GPI: Sí, pero lo encuentro más útil Stringsya que eso es básicamente con lo que trabajo el 95% del tiempo y terminas convirtiendo de todos modos.
BullyWiiPlaza
9
¡No, esto no es equivalente al código de chandpriyankara! Su código usa ECB que generalmente es inseguro y no deseado. Debe especificar explícitamente CBC. Cuando se especifica CBC, su código se romperá.
Dan
Perfectamente funcional, completamente inseguro y con muy malas prácticas de programación. la clase tiene mal nombre. El tamaño de la clave no se comprueba de antemano. Pero lo más importante es que el código usa el modo ECB inseguro, ocultando el problema en la pregunta original . Finalmente, no especifica una codificación de caracteres, lo que significa que la decodificación de texto puede fallar en otras plataformas.
Maarten Bodewes
24

Me parece que no está tratando correctamente con su vector de inicialización (IV). Ha pasado mucho tiempo desde la última vez que leí sobre AES, IV y encadenamiento de bloques, pero su línea

IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());

no parece estar bien. En el caso de AES, puede pensar en el vector de inicialización como el "estado inicial" de una instancia de cifrado, y este estado es un poco de información que no puede obtener de su clave, sino del cálculo real del cifrado de cifrado. (Se podría argumentar que si el IV pudiera extraerse de la clave, entonces no sería útil, ya que la clave ya se le dio a la instancia de cifrado durante su fase de inicio).

Por lo tanto, debe obtener el IV como un byte [] de la instancia de cifrado al final de su cifrado

  cipherOutputStream.close();
  byte[] iv = encryptCipher.getIV();

y usted debe inicializar su Cipheren DECRYPT_MODEeste byte []:

  IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

Entonces, su descifrado debería estar bien. Espero que esto ayude.

GPI
fuente
Gracias por ayudar a un novato. Adopté este ejemplo de otras publicaciones. ¿Supongo que no sabes cómo evitar la necesidad de una vía intravenosa? He visto, pero no he probado, otros ejemplos de AES que no lo usan.
TedTrippin
Ignora eso, ¡he encontrado la respuesta! Necesito usar AES / ECB / PKCS5Padding.
TedTrippin
20
La mayoría de las veces no desea utilizar ECB. Solo google por qué.
João Fernandes
2
@Mushy: estuvo de acuerdo en que elegir y establecer explícitamente un IV, de una fuente aleatoria confiable, es mejor que dejar que la instancia de Cihper elija uno. Por otro lado, esta respuesta aborda el problema original de confundir el vector de inicialización para la clave. Por eso se votó a favor al principio. Ahora, esta publicación se ha convertido más en un punto de referencia de código de muestra, y la gente aquí hizo un gran ejemplo, justo al lado de lo que trataba la pregunta original.
GPI
3
@GPI Upvoted. Los otros "grandes ejemplos" no son tan buenos, y en realidad no abordan la pregunta en absoluto. En cambio, este parece haber sido el lugar para que los novatos copien ciegamente muestras criptográficas sin comprender que puede haber posibles problemas de seguridad y, como siempre, los hay.
Maarten Bodewes
17

El IV que está utilizando para el descifrado es incorrecto. Reemplazar este código

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(aesKey.getEncoded());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Con este código

//Decrypt cipher
Cipher decryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivParameterSpec = new IvParameterSpec(encryptCipher.getIV());
decryptCipher.init(Cipher.DECRYPT_MODE, aesKey, ivParameterSpec);

Y eso debería resolver tu problema.


A continuación se incluye un ejemplo de una clase AES simple en Java. No recomiendo usar esta clase en entornos de producción, ya que es posible que no tenga en cuenta todas las necesidades específicas de su aplicación.

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class AES 
{
    public static byte[] encrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.ENCRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    public static byte[] decrypt(final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {       
        return AES.transform(Cipher.DECRYPT_MODE, keyBytes, ivBytes, messageBytes);
    }

    private static byte[] transform(final int mode, final byte[] keyBytes, final byte[] ivBytes, final byte[] messageBytes) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        final SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
        final IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        byte[] transformedBytes = null;

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

            cipher.init(mode, keySpec, ivSpec);

            transformedBytes = cipher.doFinal(messageBytes);
        }        
        catch (NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) 
        {
            e.printStackTrace();
        }
        return transformedBytes;
    }

    public static void main(final String[] args) throws InvalidKeyException, InvalidAlgorithmParameterException
    {
        //Retrieved from a protected local file.
        //Do not hard-code and do not version control.
        final String base64Key = "ABEiM0RVZneImaq7zN3u/w==";

        //Retrieved from a protected database.
        //Do not hard-code and do not version control.
        final String shadowEntry = "AAECAwQFBgcICQoLDA0ODw==:ZtrkahwcMzTu7e/WuJ3AZmF09DE=";

        //Extract the iv and the ciphertext from the shadow entry.
        final String[] shadowData = shadowEntry.split(":");        
        final String base64Iv = shadowData[0];
        final String base64Ciphertext = shadowData[1];

        //Convert to raw bytes.
        final byte[] keyBytes = Base64.getDecoder().decode(base64Key);
        final byte[] ivBytes = Base64.getDecoder().decode(base64Iv);
        final byte[] encryptedBytes = Base64.getDecoder().decode(base64Ciphertext);

        //Decrypt data and do something with it.
        final byte[] decryptedBytes = AES.decrypt(keyBytes, ivBytes, encryptedBytes);

        //Use non-blocking SecureRandom implementation for the new IV.
        final SecureRandom secureRandom = new SecureRandom();

        //Generate a new IV.
        secureRandom.nextBytes(ivBytes);

        //At this point instead of printing to the screen, 
        //one should replace the old shadow entry with the new one.
        System.out.println("Old Shadow Entry      = " + shadowEntry);
        System.out.println("Decrytped Shadow Data = " + new String(decryptedBytes, StandardCharsets.UTF_8));
        System.out.println("New Shadow Entry      = " + Base64.getEncoder().encodeToString(ivBytes) + ":" + Base64.getEncoder().encodeToString(AES.encrypt(keyBytes, ivBytes, decryptedBytes)));
    }
}

Tenga en cuenta que AES no tiene nada que ver con la codificación, por lo que elegí manejarlo por separado y sin la necesidad de bibliotecas de terceros.

k170
fuente
En primer lugar, no ha respondido a la pregunta original. En segundo lugar, ¿por qué responde a una pregunta ya respondida y bien aceptada? Pensé que se suponía que la protección detendría este spam.
TedTrippin
14
Al igual que la respuesta aceptada, elegí responder a su pregunta con un ejemplo. Proporcioné un fragmento de código completamente funcional, que le muestra cómo manejar adecuadamente el vector de inicialización, entre otras cosas. En cuanto a su segunda pregunta, sentí que se necesitaba una respuesta actualizada ya que el códec Apache ya no es necesario. Entonces no, esto no es spam. Deja de trippin.
k170
7
Un IV tiene un propósito específico que es aleatorizar el texto cifrado y proporcionar seguridad semántica. Si usa el mismo par clave + IV, los atacantes pueden determinar si ha enviado un mensaje con el mismo prefijo que antes. El IV no tiene que ser secreto, pero tiene que ser impredecible. Una forma común es simplemente prefijar el IV al texto cifrado y cortarlo antes de descifrarlo.
Artjom B.
4
voto negativo: IV codificado, ver Artjom B. comentar arriba por qué es malo
Murmel
1
El modo CTR debe combinarse con NoPadding. El modo CTR luego, no se requiere en lugar de CBC (a menos que apliquen oráculos de relleno), pero si el CTR se utiliza, a continuación, utilizar "/NoPadding". CTR es un modo que convierte AES en un cifrado de flujo, y un cifrado de flujo opera en bytes en lugar de bloques.
Maarten Bodewes
16

En esta respuesta, elijo abordar el tema principal del "Ejemplo simple de cifrado / descifrado AES de Java" y no la pregunta de depuración específica porque creo que esto beneficiará a la mayoría de los lectores.

Este es un resumen simple de mi publicación de blog sobre el cifrado AES en Java por lo que recomiendo leerlo antes de implementar cualquier cosa. Sin embargo, seguiré brindando un ejemplo simple para usar y dar algunos consejos sobre qué tener en cuenta.

En este ejemplo, elegiré utilizar cifrado autenticado con el modo Galois / Counter o el modo GCM . La razón es que, en la mayoría de los casos, desea integridad y autenticidad en combinación con confidencialidad (lea más en el blog ).

Tutorial de cifrado / descifrado AES-GCM

Estos son los pasos necesarios para cifrar / descifrar con AES-GCM con Java Cryptography Architecture (JCA) . No mezclar con otros ejemplos , ya que las diferencias sutiles pueden hacer que su código sea completamente inseguro.

1. Crear clave

Como depende de su caso de uso, asumiré el caso más simple: una clave secreta aleatoria.

SecureRandom secureRandom = new SecureRandom();
byte[] key = new byte[16];
secureRandom.nextBytes(key);
SecretKey secretKey = SecretKeySpec(key, "AES");

Importante:

2. Cree el vector de inicialización

Se utiliza un vector de inicialización (IV) para que la misma clave secreta cree diferentes textos cifrados .

byte[] iv = new byte[12]; //NEVER REUSE THIS IV WITH SAME KEY
secureRandom.nextBytes(iv);

Importante:

3. Cifrar con IV y Key

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv); //128 bit auth tag length
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] cipherText = cipher.doFinal(plainText);

Importante:

  • use una etiqueta de autenticación de 16 bytes / 128 bits (utilizada para verificar la integridad / autenticidad)
  • la etiqueta de autenticación se agregará automáticamente al texto cifrado (en la implementación de JCA)
  • dado que GCM se comporta como un cifrado de flujo, no se requiere relleno
  • se utiliza CipherInputStreamal cifrar grandes cantidades de datos
  • ¿Quiere que se verifiquen datos adicionales (no secretos) si se modificaron? Es posible que desee utilizar datos asociados con cipher.updateAAD(associatedData); Más aquí.

3. Serializar en un solo mensaje

Solo agregue IV y texto cifrado. Como se indicó anteriormente, el IV no tiene por qué ser secreto.

ByteBuffer byteBuffer = ByteBuffer.allocate(iv.length + cipherText.length);
byteBuffer.put(iv);
byteBuffer.put(cipherText);
byte[] cipherMessage = byteBuffer.array();

Opcionalmente, codifique con Base64 si necesita una representación de cadena. Utilice la implementación incorporada de Android o Java 8 (no use Apache Commons Codec, es una implementación terrible). La codificación se utiliza para "convertir" matrices de bytes en una representación de cadena para que sea ASCII seguro, por ejemplo:

String base64CipherMessage = Base64.getEncoder().encodeToString(cipherMessage);

4. Preparar el descifrado: deserializar

Si ha codificado el mensaje, primero decodifíquelo en una matriz de bytes:

byte[] cipherMessage = Base64.getDecoder().decode(base64CipherMessage)

Importante:

5. Descifrar

Inicialice el cifrado y establezca los mismos parámetros que con el cifrado:

final Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
//use first 12 bytes for iv
AlgorithmParameterSpec gcmIv = new GCMParameterSpec(128, cipherMessage, 0, 12);
cipher.init(Cipher.DECRYPT_MODE, secretKey, gcmIv);
//use everything from 12 bytes on as ciphertext
byte[] plainText = cipher.doFinal(cipherMessage, 12, cipherMessage.length - 12);

Importante:

  • no olvide agregar datos asociados con cipher.updateAAD(associatedData);si los agregó durante el cifrado.

Se puede encontrar un fragmento de código funcional en esta esencia.


Tenga en cuenta que las implementaciones más recientes de Android (SDK 21+) y Java (7+) deben tener AES-GCM. Es posible que las versiones anteriores no lo tengan. Sigo eligiendo este modo, ya que es más fácil de implementar además de ser más eficiente en comparación con un modo similar de Encrypt-then-Mac (por ejemplo, con AES-CBC + HMAC ). Consulte este artículo sobre cómo implementar AES-CBC con HMAC .

Patrick Favre
fuente
El problema es que pedir ejemplos está explícitamente fuera de tema en SO. Y el mayor problema es que se trata de fragmentos de código no revisados ​​que son difíciles de validar. Agradezco el esfuerzo, pero no creo que SO deba ser el lugar para esto.
Maarten Bodewes
1
Sin embargo, admiro el esfuerzo, así que solo señalaré un solo error: "el iv debe ser impredecible en combinación con ser único (es decir, usar iv aleatorio)"; esto es cierto para el modo CBC pero no para GCM.
Maarten Bodewes
this is true for CBC mode but not for GCM¿Te refieres a la parte completa, o solo que no necesita ser impredecible?
Patrick Favre
1
"Si no entiendes el tema, probablemente no deberías usar primitivas de bajo nivel en primer lugar" seguro, DEBE ser el caso, muchos desarrolladores todavía lo hacen. No estoy seguro de que abstenerse de publicar contenido de alta calidad sobre seguridad / criptografía en lugares donde a menudo no hay mucho es la solución adecuada para esto. - gracias por señalar mi error por cierto
Patrick Favre
1
Bien, solo porque me gusta la respuesta con el contenido (en lugar del propósito): el manejo de IV se puede simplificar especialmente durante el descifrado: después de todo, Java facilita la creación de un IV directamente desde una matriz de bytes existente. Lo mismo ocurre con el descifrado, que no tiene que comenzar en el desplazamiento 0. Toda esta copia simplemente no es necesaria. Además, si tiene que enviar una longitud para el IV (¿verdad?) Entonces, ¿por qué no usar un solo byte (sin firmar)? No va a pasar de 255 bytes para el IV, ¿verdad?
Maarten Bodewes
2

Versión ejecutable del editor en línea: -

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//import org.apache.commons.codec.binary.Base64;
import java.util.Base64;

public class Encryptor {
    public static String encrypt(String key, String initVector, String value) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());

            //System.out.println("encrypted string: "
              //      + Base64.encodeBase64String(encrypted));

            //return Base64.encodeBase64String(encrypted);
            String s = new String(Base64.getEncoder().encode(encrypted));
            return s;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String initVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
            SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) {
        String key = "Bar12345Bar12345"; // 128 bit key
        String initVector = "RandomInitVector"; // 16 bytes IV

        System.out.println(encrypt(key, initVector, "Hello World"));
        System.out.println(decrypt(key, initVector, encrypt(key, initVector, "Hello World")));
    }
}
Pantalón Bhupesh
fuente
Genial, feliz de que haya ayudado!
Bhupesh Pant
Una contraseña no es una clave, un IV no debe ser estático. Código aún escrito en cadena, lo que hace que sea imposible destruir la clave. No hay indicación de qué hacer con el intravenoso, ni ninguna idea de que debería ser impredecible.
Maarten Bodewes
1

A menudo es una buena idea confiar en la solución estándar proporcionada por la biblioteca:

private static void stackOverflow15554296()
    throws
        NoSuchAlgorithmException, NoSuchPaddingException,        
        InvalidKeyException, IllegalBlockSizeException,
        BadPaddingException
{

    // prepare key
    KeyGenerator keygen = KeyGenerator.getInstance("AES");
    SecretKey aesKey = keygen.generateKey();
    String aesKeyForFutureUse = Base64.getEncoder().encodeToString(
            aesKey.getEncoded()
    );

    // cipher engine
    Cipher aesCipher = Cipher.getInstance("AES/ECB/PKCS5Padding");

    // cipher input
    aesCipher.init(Cipher.ENCRYPT_MODE, aesKey);
    byte[] clearTextBuff = "Text to encode".getBytes();
    byte[] cipherTextBuff = aesCipher.doFinal(clearTextBuff);

    // recreate key
    byte[] aesKeyBuff = Base64.getDecoder().decode(aesKeyForFutureUse);
    SecretKey aesDecryptKey = new SecretKeySpec(aesKeyBuff, "AES");

    // decipher input
    aesCipher.init(Cipher.DECRYPT_MODE, aesDecryptKey);
    byte[] decipheredBuff = aesCipher.doFinal(cipherTextBuff);
    System.out.println(new String(decipheredBuff));
}

Esto imprime "Texto para codificar".

La solución se basa en la Guía de referencia de la arquitectura de criptografía de Java y la respuesta https://stackoverflow.com/a/20591539/146745 .

andrej
fuente
5
Nunca use el modo ECB. Período.
Konstantino Sparakis
1
ECB no debe utilizarse si se cifra más de un bloque de datos con la misma clave, por lo que para el "Texto para codificar" es suficientemente bueno. stackoverflow.com/a/1220869/146745
andrej
La clave @AndroidDev se genera en la sección de preparación de claves: aesKey = keygen.generateKey ()
andrej
1

Esta es una mejora con respecto a la respuesta aceptada.

Cambios:

(1) Usar IV aleatorio y anteponerlo al texto cifrado

(2) Usar SHA-256 para generar una clave a partir de una frase de contraseña

(3) Sin dependencia de Apache Commons

public static void main(String[] args) throws GeneralSecurityException {
    String plaintext = "Hello world";
    String passphrase = "My passphrase";
    String encrypted = encrypt(passphrase, plaintext);
    String decrypted = decrypt(passphrase, encrypted);
    System.out.println(encrypted);
    System.out.println(decrypted);
}

private static SecretKeySpec getKeySpec(String passphrase) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    return new SecretKeySpec(digest.digest(passphrase.getBytes(UTF_8)), "AES");
}

private static Cipher getCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
    return Cipher.getInstance("AES/CBC/PKCS5PADDING");
}

public static String encrypt(String passphrase, String value) throws GeneralSecurityException {
    byte[] initVector = new byte[16];
    SecureRandom.getInstanceStrong().nextBytes(initVector);
    Cipher cipher = getCipher();
    cipher.init(Cipher.ENCRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] encrypted = cipher.doFinal(value.getBytes());
    return DatatypeConverter.printBase64Binary(initVector) +
            DatatypeConverter.printBase64Binary(encrypted);
}

public static String decrypt(String passphrase, String encrypted) throws GeneralSecurityException {
    byte[] initVector = DatatypeConverter.parseBase64Binary(encrypted.substring(0, 24));
    Cipher cipher = getCipher();
    cipher.init(Cipher.DECRYPT_MODE, getKeySpec(passphrase), new IvParameterSpec(initVector));
    byte[] original = cipher.doFinal(DatatypeConverter.parseBase64Binary(encrypted.substring(24)));
    return new String(original);
}
wvdz
fuente
Un hash todavía no es una función de generación de claves basada en contraseña / PBKDF. O usa una clave aleatoria o usa un PBKDF como PBKDF2 / Cifrado basado en contraseña.
Maarten Bodewes
@MaartenBodewes ¿Puede sugerir una mejora?
wvdz
PBKDF2 está presente en Java, así que creo que acabo de sugerir uno. De acuerdo, no codifiqué uno, pero en mi opinión, eso es pedir demasiado. Hay muchos ejemplos de cifrado basado en contraseña.
Maarten Bodewes
@MaartenBodewes Pensé que podría ser una solución simple. Por curiosidad, ¿cuáles serían las vulnerabilidades específicas al usar este código tal cual?
wvdz
0

Otra solución usando java.util.Base64 con Spring Boot

Clase de cifrador

package com.jmendoza.springboot.crypto.cipher;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

@Component
public class Encryptor {

    @Value("${security.encryptor.key}")
    private byte[] key;
    @Value("${security.encryptor.algorithm}")
    private String algorithm;

    public String encrypt(String plainText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        return new String(Base64.getEncoder().encode(cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8))));
    }

    public String decrypt(String cipherText) throws Exception {
        SecretKeySpec secretKey = new SecretKeySpec(key, algorithm);
        Cipher cipher = Cipher.getInstance(algorithm);
        cipher.init(Cipher.DECRYPT_MODE, secretKey);
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)));
    }
}

EncryptorController (clase)

package com.jmendoza.springboot.crypto.controller;

import com.jmendoza.springboot.crypto.cipher.Encryptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/cipher")
public class EncryptorController {

    @Autowired
    Encryptor encryptor;

    @GetMapping(value = "encrypt/{value}")
    public String encrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.encrypt(value);
    }

    @GetMapping(value = "decrypt/{value}")
    public String decrypt(@PathVariable("value") final String value) throws Exception {
        return encryptor.decrypt(value);
    }
}

application.properties

server.port=8082
security.encryptor.algorithm=AES
security.encryptor.key=M8jFt46dfJMaiJA0

Ejemplo

http: // localhost: 8082 / cipher / encrypt / jmendoza

2h41HH8Shzc4BRU3hVDOXA ==

http: // localhost: 8082 / cipher / decrypt / 2h41HH8Shzc4BRU3hVDOXA ==

jmendoza

Jonathan Mendoza
fuente
-1

Versión optimizada de la respuesta aceptada.

  • sin bibliotecas de terceros

  • incluye IV en el mensaje cifrado (puede ser público)

  • la contraseña puede tener cualquier longitud

Código:

import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class Encryptor {
    public static byte[] getRandomInitialVector() {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            SecureRandom randomSecureRandom = SecureRandom.getInstance("SHA1PRNG");
            byte[] initVector = new byte[cipher.getBlockSize()];
            randomSecureRandom.nextBytes(initVector);
            return initVector;
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static byte[] passwordTo16BitKey(String password) {
        try {
            byte[] srcBytes = password.getBytes("UTF-8");
            byte[] dstBytes = new byte[16];

            if (srcBytes.length == 16) {
                return srcBytes;
            }

            if (srcBytes.length < 16) {
                for (int i = 0; i < dstBytes.length; i++) {
                    dstBytes[i] = (byte) ((srcBytes[i % srcBytes.length]) * (srcBytes[(i + 1) % srcBytes.length]));
                }
            } else if (srcBytes.length > 16) {
                for (int i = 0; i < srcBytes.length; i++) {
                    dstBytes[i % dstBytes.length] += srcBytes[i];
                }
            }

            return dstBytes;
        } catch (UnsupportedEncodingException ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String encrypt(String key, String value) {
        return encrypt(passwordTo16BitKey(key), value);
    }

    public static String encrypt(byte[] key, String value) {
        try {
            byte[] initVector = Encryptor.getRandomInitialVector();
            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

            byte[] encrypted = cipher.doFinal(value.getBytes());
            return Base64.getEncoder().encodeToString(encrypted) + " " + Base64.getEncoder().encodeToString(initVector);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }

    public static String decrypt(String key, String encrypted) {
        return decrypt(passwordTo16BitKey(key), encrypted);
    }

    public static String decrypt(byte[] key, String encrypted) {
        try {
            String[] encryptedParts = encrypted.split(" ");
            byte[] initVector = Base64.getDecoder().decode(encryptedParts[1]);
            if (initVector.length != 16) {
                return null;
            }

            IvParameterSpec iv = new IvParameterSpec(initVector);
            SecretKeySpec skeySpec = new SecretKeySpec(key, "AES");

            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);

            byte[] original = cipher.doFinal(Base64.getDecoder().decode(encryptedParts[0]));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return null;
    }
}

Uso:

String key = "Password of any length.";
String encrypted = Encryptor.encrypt(key, "Hello World");
String decrypted = Encryptor.decrypt(key, encrypted);
System.out.println(encrypted);
System.out.println(decrypted);

Salida de ejemplo:

QngBg+Qc5+F8HQsksgfyXg== yDfYiIHTqOOjc0HRNdr1Ng==
Hello World
ue7m
fuente
Su función de derivación de contraseña no es segura. No esperaría e.printStackTrace()en el llamado código optimizado.
Maarten Bodewes