Java AES y usando mi propia clave

88

Quiero encriptar una cadena usando AES con mi propia clave. Pero tengo problemas con la longitud de bits de la clave. ¿Puede revisar mi código y ver qué necesito corregir / cambiar?

public static void main(String[] args) throws Exception {
    String username = "[email protected]";
    String password = "Password1";
    String secretID = "BlahBlahBlah";
    String SALT2 = "deliciously salty";

    // Get the Key
    byte[] key = (SALT2 + username + password).getBytes();
    System.out.println((SALT2 + username + password).getBytes().length);

    // Need to pad key for AES
    // TODO: Best way?

    // Generate the secret key specs.
    SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

    // Instantiate the cipher
    Cipher cipher = Cipher.getInstance("AES");
    cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

    byte[] encrypted = cipher.doFinal((secrectID).getBytes());
    System.out.println("encrypted string: " + asHex(encrypted));

    cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
    byte[] original = cipher.doFinal(encrypted);
    String originalString = new String(original);
    System.out.println("Original string: " + originalString + "\nOriginal string (Hex): " + asHex(original));
}

Ahora mismo obtengo una excepción " Longitud de clave AES no válida: 86 bytes ". ¿Necesito rellenar mi llave? ¿Cómo debería hacerlo?

¿También necesito configurar algo para ECB o CBC?

Gracias

Bernie Pérez
fuente
6
Me parece inquietante tu falta de sal aleatoria . Ahora en serio: en el contexto de la criptografía, SALT debería ser aleatorio
João Portela
16
Jaja divertido. De hecho, tengo una sal aleatoria, pero limpié mi código para aclarar mi pregunta. Es por eso que la variable se llama SALT2. Pero una buena referencia para otros que se encuentran con el mismo problema y les gusta copiar / pegar código.
Bernie Perez

Respuestas:

125

Editar:

Como está escrito en los comentarios, el código antiguo no es la "mejor práctica". Debe utilizar un algoritmo de generación de claves como PBKDF2 con un alto número de iteraciones. También debe usar al menos parcialmente una sal no estática (que significa para cada "identidad" exclusiva) sal. Si es posible generado aleatoriamente y almacenado junto con el texto cifrado.

    SecureRandom sr = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[16];
    sr.nextBytes(salt);

    PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 1000, 128 * 8);
    SecretKey key = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1").generateSecret(spec);
    Cipher aes = Cipher.getInstance("AES");
    aes.init(Cipher.ENCRYPT_MODE, key);

===========

Respuesta antigua

Debe usar SHA-1 para generar un hash a partir de su clave y recortar el resultado a 128 bits (16 bytes).

Además, no genere matrices de bytes desde Strings a través de getBytes () , utiliza el conjunto de caracteres predeterminado de la plataforma. Entonces, la contraseña "blaöä" da como resultado una matriz de bytes diferente en diferentes plataformas.

byte[] key = (SALT2 + username + password).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA-1");
key = sha.digest(key);
key = Arrays.copyOf(key, 16); // use only first 128 bit

SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

Editar: Si necesita 256 bits como tamaños de clave, debe descargar el enlace de descarga de Oracle "Archivos de política de jurisdicción de fuerza ilimitada de Java Cryptography Extension (JCE)" , use SHA-256 como hash y elimine la línea Arrays.copyOf . "ECB" es el modo de cifrado predeterminado y "PKCS5Padding" el relleno predeterminado. Puede usar diferentes modos de cifrado y modos de relleno a través de la cadena Cipher.getInstance usando el siguiente formato: "Cipher / Mode / Padding"

Para AES que usa CTS y PKCS5Padding, la cadena es: "AES / CTS / PKCS5Padding"

mknjc
fuente
Esto funcionará, pero su contraseña es hash, y luego solo usa los primeros bits. ¿No hay mejor manera de hacer esto?
Bernie Perez
4
No hay mejor manera de generar la clave porque AES necesita una clave de 128/192/256 bits. Si no aplica hash a su clave y solo recorta la entrada, solo usaría los primeros 16/24/32 Bytes. Entonces, generar un Hash es la única forma razonable.
mknjc
13
Tenga en cuenta que esta respuesta no utiliza una buena función de derivación de claves y, por lo tanto, no es tan segura como debería ser . Vea la otra respuesta para una función de derivación de clave ligeramente desactualizada y, desafortunadamente, sigue siendo una sal estática.
Maarten Bodewes
2
¿Puedo sugerir eliminar esta respuesta, ya que es una práctica extremadamente mala? Se debe utilizar una función de derivación de clave adecuada, al menos PBKDF2.
Boris the Spider
1
Sí, la respuesta es muy mala, como dijo Maarten hace años. Consulte esta respuesta de la función de derivación de claves y criptografía
kelalaka
14

Debe utilizar un generador de claves para generar la clave,

Las longitudes de las claves AES son de 128, 192 y 256 bits, según el cifrado que desee utilizar.

Eche un vistazo al tutorial aquí

Aquí está el código para el cifrado basado en contraseña, este tiene la contraseña que se ingresa a través de System.in puede cambiarlo para usar una contraseña almacenada si lo desea.

        PBEKeySpec pbeKeySpec;
        PBEParameterSpec pbeParamSpec;
        SecretKeyFactory keyFac;

        // Salt
        byte[] salt = {
            (byte)0xc7, (byte)0x73, (byte)0x21, (byte)0x8c,
            (byte)0x7e, (byte)0xc8, (byte)0xee, (byte)0x99
        };

        // Iteration count
        int count = 20;

        // Create PBE parameter set
        pbeParamSpec = new PBEParameterSpec(salt, count);

        // Prompt user for encryption password.
        // Collect user password as char array (using the
        // "readPassword" method from above), and convert
        // it into a SecretKey object, using a PBE key
        // factory.
        System.out.print("Enter encryption password:  ");
        System.out.flush();
        pbeKeySpec = new PBEKeySpec(readPassword(System.in));
        keyFac = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
        SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

        // Create PBE Cipher
        Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES");

        // Initialize PBE Cipher with key and parameters
        pbeCipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

        // Our cleartext
        byte[] cleartext = "This is another example".getBytes();

        // Encrypt the cleartext
        byte[] ciphertext = pbeCipher.doFinal(cleartext);
Keibosh
fuente
3
¿Cómo genero mi clave con la contraseña usando KeyGenerator? Quiero generar la misma clave basada en la contraseña. Entonces puedo descifrar la cadena más tarde.
Bernie Perez
De lo que estás hablando es de cifrado basado en contraseña, no de AES.
Actualicé
5
Intente usar el generador de claves PBEKDF2 en su lugar, usando la cadena "PBKDF2WithHmacSHA1" SecretKeyFactorypara obtener un cifrado más actualizado.
Maarten Bodewes
12
En realidad, todas las primitivas criptográficas utilizadas en esta respuesta están desactualizadas , MD5 y DES con seguridad. Presta atención.
Maarten Bodewes
MD5 y DES son conjuntos de cifrado débiles y deben EVITARSE
atom88
6
import java.security.Key;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.*;
import java.io.BufferedReader;
import java.io.FileReader;

public class AESFile 
{
private static String algorithm = "AES";
private static byte[] keyValue=new byte[] {'0','2','3','4','5','6','7','8','9','1','2','3','4','5','6','7'};// your key

    // Performs Encryption
    public static String encrypt(String plainText) throws Exception 
    {
            Key key = generateKey();
            Cipher chiper = Cipher.getInstance(algorithm);
            chiper.init(Cipher.ENCRYPT_MODE, key);
            byte[] encVal = chiper.doFinal(plainText.getBytes());
            String encryptedValue = new BASE64Encoder().encode(encVal);
            return encryptedValue;
    }

    // Performs decryption
    public static String decrypt(String encryptedText) throws Exception 
    {
            // generate key 
            Key key = generateKey();
            Cipher chiper = Cipher.getInstance(algorithm);
            chiper.init(Cipher.DECRYPT_MODE, key);
            byte[] decordedValue = new BASE64Decoder().decodeBuffer(encryptedText);
            byte[] decValue = chiper.doFinal(decordedValue);
            String decryptedValue = new String(decValue);
            return decryptedValue;
    }

//generateKey() is used to generate a secret key for AES algorithm
    private static Key generateKey() throws Exception 
    {
            Key key = new SecretKeySpec(keyValue, algorithm);
            return key;
    }

    // performs encryption & decryption 
    public static void main(String[] args) throws Exception 
    {
        FileReader file = new FileReader("C://myprograms//plaintext.txt");
        BufferedReader reader = new BufferedReader(file);
        String text = "";
        String line = reader.readLine();
    while(line!= null)
        {
            text += line;
    line = reader.readLine();
        }
        reader.close();
    System.out.println(text);

            String plainText = text;
            String encryptedText = AESFile.encrypt(plainText);
            String decryptedText = AESFile.decrypt(encryptedText);

            System.out.println("Plain Text : " + plainText);
            System.out.println("Encrypted Text : " + encryptedText);
            System.out.println("Decrypted Text : " + decryptedText);
    }
}
Shankar Murthy
fuente
5
Quizás agregue más texto explicativo.
ChrisG
Pregunta, ¿cuál es el punto de tener keyValue, con la matriz de bytes? Veo que se está utilizando para hacer la Llave, ¿por qué? ¿Se puede hacer algo usando like en su SecretKeylugar? ¿Si es así, cómo?
Austin
@Mandrek, se cifrará el contenido del archivo "plaintext.txt". La lógica anterior cifra los datos / mensaje en el archivo que se lee como argumento en el constructor FileReader.
Shankar Murthy
2

Esto funcionará.

public class CryptoUtils {

    private  final String TRANSFORMATION = "AES";
    private  final String encodekey = "1234543444555666";
    public  String encrypt(String inputFile)
            throws CryptoException {
        return doEncrypt(encodekey, inputFile);
    }


    public  String decrypt(String input)
            throws CryptoException {
    // return  doCrypto(Cipher.DECRYPT_MODE, key, inputFile);
    return doDecrypt(encodekey,input);
    }

    private  String doEncrypt(String encodekey, String inputStr)   throws CryptoException {
        try {

            Cipher cipher = Cipher.getInstance(TRANSFORMATION);

            byte[] key = encodekey.getBytes("UTF-8");
            MessageDigest sha = MessageDigest.getInstance("SHA-1");
            key = sha.digest(key);
            key = Arrays.copyOf(key, 16); // use only first 128 bit

            SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

            byte[] inputBytes = inputStr.getBytes();     
            byte[] outputBytes = cipher.doFinal(inputBytes);

            return Base64Utils.encodeToString(outputBytes);

        } catch (NoSuchPaddingException | NoSuchAlgorithmException
                | InvalidKeyException | BadPaddingException
                | IllegalBlockSizeException | IOException ex) {
            throw new CryptoException("Error encrypting/decrypting file", ex);
       }
     }


    public  String doDecrypt(String encodekey,String encrptedStr) { 
          try {     

              Cipher dcipher = Cipher.getInstance(TRANSFORMATION);
              dcipher = Cipher.getInstance("AES");
              byte[] key = encodekey.getBytes("UTF-8");
              MessageDigest sha = MessageDigest.getInstance("SHA-1");
              key = sha.digest(key);
              key = Arrays.copyOf(key, 16); // use only first 128 bit

              SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");

              dcipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            // decode with base64 to get bytes

              byte[] dec = Base64Utils.decode(encrptedStr.getBytes());  
              byte[] utf8 = dcipher.doFinal(dec);

              // create new string based on the specified charset
              return new String(utf8, "UTF8");

          } catch (Exception e) {

            e.printStackTrace();

          }
      return null;
      }
 }
Taran
fuente
2

MD5, AES, sin relleno

import static javax.crypto.Cipher.DECRYPT_MODE;
import static javax.crypto.Cipher.ENCRYPT_MODE;
import static org.apache.commons.io.Charsets.UTF_8;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;

public class PasswordUtils {

    private PasswordUtils() {}

    public static String encrypt(String text, String pass) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(ENCRYPT_MODE, key);

            byte[] encrypted = cipher.doFinal(text.getBytes(UTF_8));
            byte[] encoded = Base64.getEncoder().encode(encrypted);
            return new String(encoded, UTF_8);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException("Cannot encrypt", e);
        }
    }

    public static String decrypt(String text, String pass) {
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            Key key = new SecretKeySpec(messageDigest.digest(pass.getBytes(UTF_8)), "AES");
            Cipher cipher = Cipher.getInstance("AES");
            cipher.init(DECRYPT_MODE, key);

            byte[] decoded = Base64.getDecoder().decode(text.getBytes(UTF_8));
            byte[] decrypted = cipher.doFinal(decoded);
            return new String(decrypted, UTF_8);

        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
            throw new RuntimeException("Cannot decrypt", e);
        }
    }
}
Miguel
fuente
Cómo crear una clave segura como SecretKeySpec en angular (ionic 4);
Nitin Karale
0
    byte[] seed = (SALT2 + username + password).getBytes();
    SecureRandom random = new SecureRandom(seed);
    KeyGenerator generator;
    generator = KeyGenerator.getInstance("AES");
    generator.init(random);
    generator.init(256);
    Key keyObj = generator.generateKey();
sonnykwe
fuente