Cómo cifrar cadenas en Java


150

Lo que necesito es encriptar la cadena que aparecerá en código de barras 2D (PDF-417) para que cuando alguien tenga una idea para escanear no tenga nada legible.

Otros requerimientos:

  • no debería ser complicado
  • no debe consistir en infraestructura RSA, PKI, pares de claves, etc.

Debe ser lo suficientemente simple como para deshacerse de las personas que están husmeando, y fácil de descifrar para otras compañías interesadas en obtener esos datos. Nos llaman, les decimos el estándar o les damos una clave simple que luego se puede usar para descifrar.

Probablemente esas compañías podrían usar diferentes tecnologías, por lo que sería bueno cumplir con algún estándar que no esté vinculado a alguna plataforma o tecnología especial.

¿Que sugieres? ¿Hay alguna clase de Java haciendo encrypt()y decrypt()sin muchas complicaciones para lograr altos estándares de seguridad?



Advertencia . Muchas de las respuestas a continuación muestran un método u otro para realizar cualquier tipo de criptografía en Java. Las respuestas pueden no reflejar buenas prácticas criptográficas y pueden no revisarse bien; no existe la seguridad de copiar / pegar . Las respuestas al menos deberían tener en cuenta la conversión de cadenas. La pregunta real con el código de barras 2D incluido es demasiado amplia y debe requerir una solución específica del cliente.
Maarten Bodewes

Respuestas:


156

Esta es la primera página que se muestra a través de Google y las vulnerabilidades de seguridad en todas las implementaciones me ponen nervioso, así que estoy publicando esto para agregar información sobre el cifrado para otros, ya que han pasado 7 años desde la publicación original. Tengo una Maestría en Ingeniería Informática y pasé mucho tiempo estudiando y aprendiendo Criptografía, así que estoy gastando mis dos centavos para hacer de Internet un lugar más seguro.

Además, tenga en cuenta que mucha implementación puede ser segura para una situación dada, pero ¿por qué usarlos y cometer un error? Utilice las herramientas más potentes que tenga disponibles a menos que tenga una razón específica para no hacerlo. En general, recomiendo usar una biblioteca y mantenerse alejado de los detalles esenciales si es posible.

ACTUALIZACIÓN 4/5/18: Reescribí algunas partes para que sean 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

Explicaré 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 omitir todos los detalles, vaya a la nueva biblioteca de Google. Tink importe eso en su proyecto y use el modo AES-GCM para todos sus cifrados y estará seguro.

Ahora, si quieres aprender los detalles esenciales sobre cómo cifrar en Java, sigue leyendo :)

Cifrados de bloque

Lo primero que debe hacer primero es elegir una clave simétrica Block Cipher. Un Block Cipher es una función / programa de computadora utilizado para crear Pseudoaleatoriedad. La pseudoaleatoriedad es una aleatoriedad falsa que ninguna computadora que no sea una Computadora Cuántica podría distinguir entre la aleatoriedad real y la aleatoriedad real. Block Cipher es como el componente básico 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 bloque disponibles en la actualidad, 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 está realmente tan cerca de Pseudo-Random como sea posible 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 bloques de 128. Con todo, AES 128 se considera seguro, aunque se han descubierto algunas debilidades, pero 256 es tan sólido como parece.

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

Modos de cifrado

El cifrado se crea cuando toma un cifrado de bloque y 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 cifrado.

Aquí hay un ejemplo de un modo de cifrado y el modo más simple conocido como BCE para que pueda comprender visualmente lo que está sucediendo:

Modo BCE

Los modos de cifrado que verá más comúnmente en línea son los siguientes:

BCE 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 BCE, esto es malo para ocultar datos repetidos como lo muestra el famoso pingüino de Linux .Ejemplo de Linux Penguin

Al implementar en Java, tenga en cuenta que si usa el siguiente código, el modo ECB se establece de manera 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 BCE, se crearon los 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 sean 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 tengan su clave, no pueden 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 escollo de los IV en el momento en que repite uno, realmente compromete la seguridad completa de su cifrado.

Generando un IV al azar

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 resolver esto y actualizarlo, ¡sería increíble! Además, los ataques SHA1 aún no son convencionales, ya que puede demorar algunos años en que un gran clúster se rompa. Mira 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 topan con el problema de que un atacante puede voltear los datos cifrados, cambiando su valor cuando se descifra. Entonces, digamos que encripta un mensaje bancario imaginario "Vender 100", su mensaje encriptado se ve así "eu23ng", el atacante cambia un poco a "eu53ng" y, de repente, cuando desencripta 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 lo verifica para verificar que el mensaje no se haya cambiado con esta firma.

Evitaría implementar GCM debido a su complejidad. Es mejor usar la nueva biblioteca de Google Tink porque aquí nuevamente, si repite accidentalmente un IV, está comprometiendo la clave en el caso de GCM, que es el último defecto de seguridad. Los nuevos investigadores están trabajando hacia modos de encriptación resistentes a la repetición IV, donde incluso si repite la 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 está implementada correctamente, pero baja la base. 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 considerarse segura. Es por eso que debe asegurarse de utilizar las bibliotecas criptográficas adecuadas para generar la clave para usted.

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 aleatoria de claves . Esta solución utiliza un generador de números aleatorios seguro para crear una clave desde cero que puede utilizar.

La otra opción menos segura es usar la entrada del usuario, como una contraseña. El problema como discutimos 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 Google Tink tiene todo esto incorporado y deberías aprovecharlo.

Desarrolladores de Android

Un punto importante a 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 del código de Java también lo es. Eso significa que si almacena la contraseña en texto sin formato en su código. Un hacker puede recuperarlo fácilmente. Por lo general, para este tipo de cifrado, desea utilizar la Criptografía asimétrica, etc. Esto está fuera del alcance de esta publicación, así 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 forma incorrecta.

Pensamientos finales

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

Si tiene alguna pregunta o comentario, ¡no dude en comentar! La seguridad siempre está cambiando y debe hacer todo lo posible para mantenerse al día :)


15
Esta es la cosa más limpia que he visto.
Seraf

1
@SabirKhan Podría ser motivo de preocupación, pero los algoritmos centrales todavía no se han roto, por lo que no estaría demasiado preocupado por eso. En el caso de que no confíes, también visita github.com/google/keyczar . Fue desarrollado por el equipo de seguridad de Google.
Konstantino Sparakis

1
@KonstantinoSparakis: si no interpreté mal la documentación de BasicTextEncryptor y StrongTextEncryptor de Jasypt, esas clases usan DES y 3DES para el cifrado, que es exactamente lo que le dices a los lectores que no usen. En mi opinión, debe reemplazar los ejemplos de código dados con uno que haga uso de StandardPBEStringEncryptor de Jasypt y defina manualmente un algoritmo AES para usar.
xpages-noob

1
@ xpages-noob Actualicé la publicación. De hecho, encontré Google Tink, que es la biblioteca más nueva compatible con criptografía, ¡así que deberías echarle un vistazo!
Konstantino Sparakis

2
El tamaño del bloque AES es de 128 bits. En AES 256, el tamaño de la clave es de 256 bits. Del mismo modo, AES 192 y AES 128. Además, desde Java 8, el getInstanceStrong()método de Cipheres preferible a SHA1PRNG
Saptarshi Basu

110

Recomiendo usar algunos cifradores simétricos estándar que están ampliamente disponibles como DES , 3DES o AES . Si bien ese no es el algoritmo más seguro, hay un montón de implementaciones y solo tendría que dar 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 quieres 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 Cipher 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);

9
¿Puedo sugerirle que actualice este ejemplo para hacer referencia al DESedealgoritmo? Dado que esta es una pregunta (y respuesta) popular, sería una pena alentar a las personas a usar DES, ya que el cifrado es muy débil para los estándares actuales.
Duncan Jones

Algo está mal con javax.crypto.BadPaddingException: dado que el bloque final no se rellena correctamente mientras está descifrado
curiosidad

2
@Duncan Indeed DES es débil, pero supongo que AES sería preferible a DESede (también conocido como TipleDES): http://security.stackexchange.com/a/26181/69785
Piovezan

2
Esto debería actualizarse para tener AES / GCM / NoPadding, DES es vulnerable a los ataques de fuerza bruta, TripleDes tampoco se recomienda
Konstantino Sparakis

1
La respuesta de Konstantino Sparakis a continuación es MUCHO mejor que esta.
Steve

22

Advertencia

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

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

Señalado por Moussa


Estoy usando Sun's Base64Encoder / Decoder que se encuentra en Sun's JRE, para evitar otro JAR en lib. Eso es peligroso desde el punto de usar OpenJDK o el JRE de otro. Además de eso, ¿hay alguna 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

1
También utilicé esta propuesta de solución a través de sun.misc.BASE64Encoder pero cuando utilicé cadenas bastante grandes para codificar, el codificador devolvió cadenas fragmentadas (76 caracteres cada una). ¡Luego cambié a Apache Commons Codec Base64 que ofrece métodos de codificación sin fragmentación!
basZero

78
El mecanismo de cifrado que describió es MUY PELIGROSO si se usa más de una vez. esa es la razón por la que se llama One-time pad. La clave secreta puede ser recuperada fácilmente por un atacante usando 2 mensajes cifrados. xor 2 mensajes cifrados y obtienes la clave. Así de simple!
xtrem

3
Su idea no es ser pesado, solo evitar que la gente intente leer lo que está escrito en códigos de barras 2D PDF-417. Y de todos modos, solo hay algunos índices que no son cruciales para nadie ...
ante.sabo

2
OKAY. Solo me preocupa que alguien use esto como un mecanismo de encriptación.
xtrem

Para el cifrado, se puede evitar que el codificador (p. Ej., BAS64 Encoder) tenga ataques de fuerza bruta.
Jagrut Dalwadi

13

gracias he hecho esta clase usando tu código tal vez alguien lo encuentre útil

objeto crypter

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;


public class ObjectCrypter {

private Cipher deCipher;
private Cipher enCipher;
private SecretKeySpec key;
private IvParameterSpec ivSpec;


public ObjectCrypter(byte[] keyBytes,   byte[] ivBytes) {
    // wrap key data in Key/IV specs to pass to cipher


     ivSpec = new IvParameterSpec(ivBytes);
    // create the cipher with the algorithm you choose
    // see javadoc for Cipher class for more info, e.g.
    try {
         DESKeySpec dkey = new  DESKeySpec(keyBytes);
          key = new SecretKeySpec(dkey.getKey(), "DES");
         deCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
         enCipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
    } catch (NoSuchAlgorithmException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (NoSuchPaddingException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (InvalidKeyException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
public byte[] encrypt(Object obj) throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, IllegalBlockSizeException, ShortBufferException, BadPaddingException {
    byte[] input = convertToByteArray(obj);
    enCipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);

    return enCipher.doFinal(input);




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


}
public Object decrypt( byte[]  encrypted) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, IOException, ClassNotFoundException {
    deCipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

    return convertFromByteArray(deCipher.doFinal(encrypted));

}



private Object convertFromByteArray(byte[] byteObject) throws IOException,
        ClassNotFoundException {
    ByteArrayInputStream bais;

    ObjectInputStream in;
    bais = new ByteArrayInputStream(byteObject);
    in = new ObjectInputStream(bais);
    Object o = in.readObject();
    in.close();
    return o;

}



private byte[] convertToByteArray(Object complexObject) throws IOException {
    ByteArrayOutputStream baos;

    ObjectOutputStream out;

    baos = new ByteArrayOutputStream();

    out = new ObjectOutputStream(baos);

    out.writeObject(complexObject);

    out.close();

    return baos.toByteArray();

}


}

publicado una pregunta relacionada aquí !
user2023507

¿No debería ser el caso de que pasar diferentes teclas durante el cifrado y descifrado no devuelva el texto? Eso no parece estar sucediendo aquí. PD: Estoy usando diferentes objetos de esta clase para realizar esta prueba.
instanceOfObject

6

Actualización el 12-DIC-2019

A diferencia de otros modos como CBC, el modo GCM no requiere que el IV sea impredecible. El único requisito es que el IV debe ser único para cada invocación con una clave determinada. Si se repite una vez para una clave determinada, la seguridad puede verse comprometida. Una manera fácil de lograr esto es usar un IV aleatorio de un fuerte generador de números pseudoaleatorios como se muestra a continuación.

También es posible usar una secuencia o marca de tiempo como IV, pero puede que no sea tan trivial como puede parecer. Por ejemplo, si el sistema no realiza un seguimiento correcto de las secuencias ya utilizadas como IV en un almacén persistente, una invocación puede repetir un IV después de reiniciar el sistema. Del mismo modo, no hay reloj perfecto. El reloj de la computadora se reajusta, etc.

Además, la clave debe girarse después de cada 2 ^ 32 invocaciones. Para obtener más detalles sobre el requisito IV, consulte esta respuesta y las recomendaciones del NIST .


Este es el código de cifrado y descifrado que acabo de escribir en Java 8 considerando los siguientes puntos. Espero que alguien encuentre esto útil:

  1. Algoritmo de cifrado : el cifrado de bloque AES con clave de 256 bits se considera lo suficientemente seguro. Para cifrar un mensaje completo, se debe seleccionar un modo. Se recomienda el cifrado autenticado (que proporciona confidencialidad e integridad). GCM, CCM y EAX son los modos de cifrado autenticado más utilizados. Generalmente se prefiere GCM y funciona bien en arquitecturas Intel que proporcionan instrucciones dedicadas para GCM. Todos estos tres modos son modos basados ​​en CTR (basados ​​en contador) y, por lo tanto, no necesitan relleno. Como resultado, no son vulnerables a los ataques relacionados con el relleno

  2. Se requiere un vector de inicialización (IV) para GCM. El IV no es un secreto. El único requisito es que sea aleatorio o impredecible. En Java, la SecuredRandomclase está destinada a producir números pseudoaleatorios criptográficamente fuertes. El algoritmo de generación de números pseudoaleatorios puede especificarse en el getInstance()método. Sin embargo, desde Java 8, la forma recomendada es utilizar el getInstanceStrong()método que utilizará el algoritmo más fuerte configurado y proporcionado por elProvider

  3. NIST recomienda 96 bit IV para GCM para promover la interoperabilidad, eficiencia y simplicidad de diseño

  4. Para garantizar una seguridad adicional, en la siguiente implementación SecureRandomse vuelve a sembrar después de producir cada 2 ^ 16 bytes de generación de bytes pseudoaleatorios

  5. El destinatario debe conocer el IV para poder descifrar el texto cifrado. Por lo tanto, el IV debe transferirse junto con el texto cifrado. Algunas implementaciones envían el IV como AD (datos asociados), lo que significa que la etiqueta de autenticación se calculará tanto en el texto cifrado como en el IV. Sin embargo, eso no es obligatorio. El IV se puede simplemente pre-escribir con el texto cifrado porque si el IV se cambia durante la transmisión debido a un ataque deliberado o un error del sistema de red / archivo, la validación de la etiqueta de autenticación fallará de todos modos

  6. Las cadenas no deben usarse para contener el mensaje de texto claro o la clave, ya que las cadenas son inmutables y, por lo tanto, no podemos borrarlas después de su uso. Estas cadenas sin borrar permanecen en la memoria y pueden aparecer en un volcado de almacenamiento dinámico. Por la misma razón, el cliente que llama a estos métodos de cifrado o descifrado debe borrar todas las variables o matrices que contienen el mensaje o la clave después de que ya no sean necesarias.

  7. Ningún proveedor está codificado en el código siguiendo las recomendaciones generales

  8. Finalmente, para la transmisión a través de la red o el almacenamiento, la clave o el texto cifrado deben codificarse utilizando la codificación Base64. Los detalles de Base64 se pueden encontrar aquí . Se debe seguir el enfoque de Java 8

Las matrices de bytes se pueden borrar usando:

Arrays.fill(clearTextMessageByteArray, Byte.MIN_VALUE);

Sin embargo, a partir de Java 8, no hay una manera fácil de borrar SecretKeyspecy, SecretKeydado que las implementaciones de estas dos interfaces no parecen haber implementado el método destroy()de la interfaz Destroyable. En el siguiente código, se escribe un método separado para borrar SecretKeySpecy SecretKeyusar la reflexión.

La clave debe generarse utilizando uno de los dos enfoques mencionados a continuación.

Tenga en cuenta que las claves son secretos como las contraseñas, pero a diferencia de las contraseñas destinadas al uso humano, las claves deben ser utilizadas por algoritmos criptográficos y, por lo tanto, deben generarse utilizando la forma anterior.

package com.sapbasu.javastudy;

import java.lang.reflect.Field;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

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

public class Crypto {

  private static final int AUTH_TAG_SIZE = 128; // bits

  // NIST recommendation: "For IVs, it is recommended that implementations
  // restrict support to the length of 96 bits, to
  // promote interoperability, efficiency, and simplicity of design."
  private static final int IV_LEN = 12; // bytes

  // number of random number bytes generated before re-seeding
  private static final double PRNG_RESEED_INTERVAL = Math.pow(2, 16);

  private static final String ENCRYPT_ALGO = "AES/GCM/NoPadding";

  private static final List<Integer> ALLOWED_KEY_SIZES = Arrays
      .asList(new Integer[] {128, 192, 256}); // bits

  private static SecureRandom prng;

  // Used to keep track of random number bytes generated by PRNG
  // (for the purpose of re-seeding)
  private static int bytesGenerated = 0;

  public byte[] encrypt(byte[] input, SecretKeySpec key) throws Exception {

    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Length of message cannot be 0");
    }

    if (!ALLOWED_KEY_SIZES.contains(key.getEncoded().length * 8)) {
      throw new IllegalArgumentException("Size of key must be 128, 192 or 256");
    }

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);

    byte[] iv = getIV(IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    cipher.init(Cipher.ENCRYPT_MODE, key, gcmParamSpec);
    byte[] messageCipher = cipher.doFinal(input);

    // Prepend the IV with the message cipher
    byte[] cipherText = new byte[messageCipher.length + IV_LEN];
    System.arraycopy(iv, 0, cipherText, 0, IV_LEN);
    System.arraycopy(messageCipher, 0, cipherText, IV_LEN,
        messageCipher.length);
    return cipherText;
  }

  public byte[] decrypt(byte[] input, SecretKeySpec key) throws Exception {
    Objects.requireNonNull(input, "Input message cannot be null");
    Objects.requireNonNull(key, "key cannot be null");

    if (input.length == 0) {
      throw new IllegalArgumentException("Input array cannot be empty");
    }

    byte[] iv = new byte[IV_LEN];
    System.arraycopy(input, 0, iv, 0, IV_LEN);

    byte[] messageCipher = new byte[input.length - IV_LEN];
    System.arraycopy(input, IV_LEN, messageCipher, 0, input.length - IV_LEN);

    GCMParameterSpec gcmParamSpec = new GCMParameterSpec(AUTH_TAG_SIZE, iv);

    Cipher cipher = Cipher.getInstance(ENCRYPT_ALGO);
    cipher.init(Cipher.DECRYPT_MODE, key, gcmParamSpec);

    return cipher.doFinal(messageCipher);
  }

  public byte[] getIV(int bytesNum) {

    if (bytesNum < 1) throw new IllegalArgumentException(
        "Number of bytes must be greater than 0");

    byte[] iv = new byte[bytesNum];

    prng = Optional.ofNullable(prng).orElseGet(() -> {
      try {
        prng = SecureRandom.getInstanceStrong();
      } catch (NoSuchAlgorithmException e) {
        throw new RuntimeException("Wrong algorithm name", e);
      }
      return prng;
    });

    if (bytesGenerated > PRNG_RESEED_INTERVAL || bytesGenerated == 0) {
      prng.setSeed(prng.generateSeed(bytesNum));
      bytesGenerated = 0;
    }

    prng.nextBytes(iv);
    bytesGenerated = bytesGenerated + bytesNum;

    return iv;
  }

  private static void clearSecret(Destroyable key)
      throws IllegalArgumentException, IllegalAccessException,
      NoSuchFieldException, SecurityException {
    Field keyField = key.getClass().getDeclaredField("key");
    keyField.setAccessible(true);
    byte[] encodedKey = (byte[]) keyField.get(key);
    Arrays.fill(encodedKey, Byte.MIN_VALUE);
  }
}

La clave de cifrado se puede generar principalmente de dos maneras:

  • Sin ninguna contraseña

    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(KEY_LEN, SecureRandom.getInstanceStrong());
    SecretKey secretKey = keyGen.generateKey();
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);
  • Con contraseña

    SecureRandom random = SecureRandom.getInstanceStrong();
    byte[] salt = new byte[32];
    random.nextBytes(salt);
    PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterations, 
       keyLength);
    SecretKeyFactory keyFactory = 
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
    SecretKey secretKey = keyFactory.generateSecret(keySpec);
    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(),
        "AES");
    Crypto.clearSecret(secretKey);
    // After encryption or decryption with key
    Crypto.clearSecret(secretKeySpec);

Actualización basada en comentarios

Como señaló @MaartenBodewes, mi respuesta no manejó ninguna, Stringcomo lo requiere la pregunta. Por lo tanto, intentaré llenar ese vacío en caso de que alguien tropiece con esta respuesta y se pregunte sobre el manejo String.

Como se indicó anteriormente en la respuesta, el manejo de información confidencial en a no Stringes, en general, una buena idea porque Stringes inmutable y, por lo tanto, no podemos borrarlo después de su uso. Y como sabemos, incluso cuando a Stringno tiene una referencia fuerte, el recolector de basura no se apresura de inmediato a sacarlo del montón. Por lo tanto, Stringsigue existiendo en la memoria durante una ventana de tiempo desconocida a pesar de que no es accesible para el programa. El problema con eso es que un volcado del montón durante ese período de tiempo revelaría la información confidencial. Por lo tanto, siempre es mejor manejar toda la información confidencial en una matriz de bytes o matriz de caracteres y luego llenar la matriz con 0 una vez que se cumpla su propósito.

Sin embargo, con todo ese conocimiento, si todavía terminamos en una situación en la que la información confidencial que se va a cifrar se encuentra en una String, primero debemos convertirla en una matriz de bytes e invocar las funciones encrypty decryptque se introdujeron anteriormente. (La otra clave de entrada se puede generar utilizando el fragmento de código proporcionado anteriormente).

A Stringse puede convertir en bytes de la siguiente manera:

byte[] inputBytes = inputString.getBytes(StandardCharsets.UTF_8);

A partir de Java 8, Stringse almacena internamente en el montón con UTF-16codificación. Sin embargo, hemos utilizado UTF-8aquí, ya que generalmente ocupa menos espacio que UTF-16, especialmente para los caracteres ASCII.

Del mismo modo, la matriz de bytes cifrada también se puede convertir en una cadena de la siguiente manera:

String encryptedString = new String(encryptedBytes, StandardCharsets.UTF_8);

1
Por mucho que quiera votar esta respuesta, ya que parece adherirse a las prácticas de cifrado actuales, no veo ningún manejo de cadenas en absoluto, lo que lo hace más como una descripción sobre cómo usar el modo GCM. Como tal, no responde la pregunta .
Maarten Bodewes

1
@MaartenBodewes Muchas gracias por tomarse el tiempo para revisar y compartir comentarios. Escribí esto con el entendimiento de que cifrar Stringusando las funciones creadas anteriormente sería trivial. Sin embargo, después de leer su comentario, entiendo que puede no ser obvio. Seguramente editaré para agregar esos detalles.
Saptarshi Basu

5

Qué tal esto:

private static byte[] xor(final byte[] input, final byte[] secret) {
    final byte[] output = new byte[input.length];
    if (secret.length == 0) {
        throw new IllegalArgumentException("empty security key");
    }
    int spos = 0;
    for (int pos = 0; pos < input.length; ++pos) {
        output[pos] = (byte) (input[pos] ^ secret[spos]);
        ++spos;
        if (spos >= secret.length) {
            spos = 0;
        }
    }
    return output;
}

Funciona bien para mí y es bastante compacto.


¿Qué sucederá si el parámetro de entrada secret == null o input == null? trabajar con bytes en lugar de cadenas está bien, pero fue irrelevante en mi caso ... lo único que importa es que esto debe ser legible y decodificable con cualquier dispositivo, en cualquier codificación de caracteres posible ...
ante.sabo

@ ante.sabo aparentemente, arrojará un NPE. Esto es lo único que se puede hacer con NULL.
Miha_x64

Mientras se input.length <= secret.lengthmantenga y no secretse reutilice nunca, esto es seguro y se llama a one-time-pad. En los casos de input.length > secret.lengthesto es una variante del cifrado de Vigenère y se considera muy débil.
Trichner

5

Puedes usar Jasypt

Con Jasypt, cifrar y verificar una contraseña puede ser tan simple como ...

StrongTextEncryptor textEncryptor = new StrongTextEncryptor();
textEncryptor.setPassword(myEncryptionPassword);

Cifrado:

String myEncryptedText = textEncryptor.encrypt(myText);

Descifrado:

String plainText = textEncryptor.decrypt(myEncryptedText);

Gradle:

compile group: 'org.jasypt', name: 'jasypt', version: '1.9.2'

caracteristicas:

Jasypt le proporciona técnicas de cifrado unidireccionales (resumen) y bidireccionales fáciles.

API abierta para usar con cualquier proveedor de JCE, y no solo la máquina virtual Java predeterminada. Jasypt se puede usar fácilmente con proveedores conocidos como Bouncy Castle. Aprende más.

Mayor seguridad para las contraseñas de sus usuarios. Aprende más.

Soporte de cifrado binario. Jasypt permite el resumen y el cifrado de binarios (conjuntos de bytes). Cifre sus objetos o archivos cuando sea necesario (para enviarlos a través de la red, por ejemplo).

Número de cifrado de apoyo. Además de textos y binarios, permite el resumen y el cifrado de valores numéricos (BigInteger y BigDecimal, se admiten otros tipos numéricos al cifrar para la persistencia de Hibernate). Aprende más.

Completamente a prueba de hilos.

Soporte para la agrupación de encriptadores / digestores, para lograr un alto rendimiento en sistemas multiprocesador / multi-núcleo.

Incluye una versión ligera ("lite") de la biblioteca para una mejor capacidad de administración en entornos de tamaño restringido como las plataformas móviles.

Proporciona herramientas de cifrado fáciles y sin configuración para usuarios nuevos en el cifrado, y también herramientas de cifrado estándar altamente configurables para usuarios avanzados.

Hibernate 3 y 4 integración opcional para campos persistentes de sus entidades mapeadas de manera encriptada. El cifrado de campos se define en los archivos de mapeo de Hibernate, y permanece transparente para el resto de la aplicación (útil para datos personales confidenciales, bases de datos con muchos usuarios habilitados para lectura ...). Cifre textos, binarios, números, booleanos, fechas ... Obtenga más información.

Integrable a la perfección en una aplicación Spring, con características de integración específicas para Spring 2, Spring 3.0 y Spring 3.1. Todos los digestores y encriptadores en jasypt están diseñados para ser fácilmente utilizados (instanciados, inyectados con dependencia ...) de Spring. Y, debido a que son seguros para subprocesos, se pueden usar sin problemas de sincronización en un entorno orientado a un solo tono como Spring. Más información: Spring 2, Spring 3.0, Spring 3.1.

Integración opcional de Spring Security (anteriormente Acegi Security) para realizar el cifrado de contraseña y las tareas de coincidencia para el marco de seguridad, mejorando la seguridad de las contraseñas de sus usuarios mediante el uso de mecanismos de cifrado de contraseña más seguros y proporcionándole un mayor grado de configuración y control. Aprende más.

Proporciona funcionalidad avanzada para encriptar todo o parte de los archivos de configuración de una aplicación, incluida información confidencial como contraseñas de bases de datos. Integre perfectamente la configuración encriptada en aplicaciones simples, basadas en Spring y / o habilitadas para Hibernate. Aprende más.

Proporciona herramientas fáciles de usar CLI (interfaz de línea de comandos) para permitir a los desarrolladores inicializar sus datos cifrados e incluir operaciones de cifrado / descifrado / resumen en tareas de mantenimiento o scripts. Aprende más.

Se integra en Apache Wicket, para un cifrado más robusto de URL en sus aplicaciones seguras.

Guías completas y documentación de javadoc, para permitir a los desarrolladores comprender mejor lo que realmente están haciendo con sus datos.

Soporte robusto de juego de caracteres, diseñado para encriptar y digerir adecuadamente los textos cualquiera que sea el juego de caracteres original. Soporte completo para idiomas como japonés, coreano, árabe ... sin problemas de codificación o plataforma.

Capacidades de configuración de muy alto nivel: el desarrollador puede implementar trucos como indicarle a un "encriptador" que solicite a un servidor HTTPS remoto la contraseña que se utilizará para el cifrado. Le permite satisfacer sus necesidades de seguridad.


1
¿Pero qué seguridad Jasyptproporciona? No puedo entenderlo desde su sitio web. ¿Es indistinguible bajo ataques de texto sin formato elegido? ¿Integridad? Confidencialidad?
trichner

4

Aquí está mi implementación de meta64.com como Spring Singleton. Si desea crear una instancia de Ciper para cada llamada que también funcionaría, y luego podría eliminar las llamadas 'sincronizadas', pero tenga cuidado con 'Cipher' no es seguro para subprocesos.

import java.security.Key;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

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

@Component
@Scope("singleton")
public class Encryptor {

    @Value("${aeskey}")
    private String keyStr;

    private Key aesKey = null;
    private Cipher cipher = null;

    synchronized private void init() throws Exception {
        if (keyStr == null || keyStr.length() != 16) {
            throw new Exception("bad aes key configured");
        }
        if (aesKey == null) {
            aesKey = new SecretKeySpec(keyStr.getBytes(), "AES");
            cipher = Cipher.getInstance("AES");
        }
    }

    synchronized public String encrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.ENCRYPT_MODE, aesKey);
        return toHexString(cipher.doFinal(text.getBytes()));
    }

    synchronized public String decrypt(String text) throws Exception {
        init();
        cipher.init(Cipher.DECRYPT_MODE, aesKey);
        return new String(cipher.doFinal(toByteArray(text)));
    }

    public static String toHexString(byte[] array) {
        return DatatypeConverter.printHexBinary(array);
    }

    public static byte[] toByteArray(String s) {
        return DatatypeConverter.parseHexBinary(s);
    }

    /*
     * DO NOT DELETE
     * 
     * Use this commented code if you don't like using DatatypeConverter dependency
     */
    // public static String toHexStringOld(byte[] bytes) {
    // StringBuilder sb = new StringBuilder();
    // for (byte b : bytes) {
    // sb.append(String.format("%02X", b));
    // }
    // return sb.toString();
    // }
    //
    // public static byte[] toByteArrayOld(String s) {
    // int len = s.length();
    // byte[] data = new byte[len / 2];
    // for (int i = 0; i < len; i += 2) {
    // data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i +
    // 1), 16));
    // }
    // return data;
    // }
}

3
Esto encriptará con el modo ECB que es horrible. Debe configurar al menos el modo CBC o el modo GCM
Konstantino Sparakis

Gracias por la sugerencia de Konstantinto, busqué en Google y encontré un código que usa "AES / CBC / PKCS5Padding" como la cadena de inicio para Cipher, en lugar de solo "AES", pero lo investigaré más. O si lo desea, puede proporcionar la solución real, para que otros puedan ver la mejor manera. Sin embargo, aparte del detalle de CBC, creo que mi solución es la más simple y segura, y merece ser votada por encima del resto.

Sí, no te preocupes, Crypto es un tema complicado. Lamentablemente, todas las implementaciones en esta página están rotas y, lamentablemente, es la primera página que aparece cuando se utiliza Google para buscar "cómo hacer el cifrado de Java". Cuando tenga la oportunidad, intentaré arreglarlos todos.
Konstantino Sparakis

Mi ejemplo es el mismo: docs.oracle.com/javase/8/docs/technotes/guides/security/crypto/… Excepto que necesitaba Cipher.getInstance ("AES / ECB / PKCS5Padding"); Mi código asume que hay algunos archivos de propiedades con una clave de cifrado perfectamente de 16 bytes, pero para cifrar una cadena de una contraseña 'suministrada por el usuario', la página de Oracle (vinculada anteriormente) muestra la forma de hacerlo también.

1
Entonces, el problema con el BCE es que es extremadamente vulnerable al análisis de frecuencia. Existe el famoso ejemplo del pingüino de Linux, blog.filippo.io/the-ecb-penguin , vea cómo, aunque la imagen está encriptada, aún puede decir que es un pingüino. Seguí adelante y escribí mis pensamientos sobre el tema a continuación :) stackoverflow.com/a/43779197/2607972
Konstantino Sparakis

4

Aquí una solución simple con solo java.*y javax.crypto.*dependencias para el cifrado de bytes que proporciona confidencialidad e integridad . Será indistinguible bajo un ataque de texto sin formato elegido para mensajes cortos del orden de kilobytes.

Se utiliza AESen el GCMmodo sin relleno, se deriva una clave de 128 bits PBKDF2con muchas iteraciones y una sal estática de la contraseña proporcionada. Esto asegura que las contraseñas de forzamiento bruto sean difíciles y distribuya la entropía por toda la clave.

Se genera un vector de inicialización aleatorio (IV) y se antepondrá al texto cifrado. Además, el byte estático 0x01se antepone como el primer byte como una 'versión'.

Todo el mensaje entra en el código de autenticación de mensaje (MAC) generado por AES/GCM.

Aquí va, clase de cifrado de dependencias externas cero que proporciona confidencialidad e integridad :

package ch.n1b.tcrypt.utils;

import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

import javax.crypto.*;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/**
 * This class implements AES-GCM symmetric key encryption with a PBKDF2 derived password.
 * It provides confidentiality and integrity of the plaintext.
 *
 * @author Thomas Richner
 * @created 2018-12-07
 */
public class AesGcmCryptor {

    // /crypto/26783/ciphertext-and-tag-size-and-iv-transmission-with-aes-in-gcm-mode
    private static final byte VERSION_BYTE = 0x01;
    private static final int VERSION_BYTE_LENGTH = 1;
    private static final int AES_KEY_BITS_LENGTH = 128;


    // fixed AES-GCM constants
    private static final String GCM_CRYPTO_NAME = "AES/GCM/NoPadding";
    private static final int GCM_IV_BYTES_LENGTH = 12;
    private static final int GCM_TAG_BYTES_LENGTH = 16;

    // can be tweaked, more iterations = more compute intensive to brute-force password
    private static final int PBKDF2_ITERATIONS = 1024;

    // protects against rainbow tables
    private static final byte[] PBKDF2_SALT = hexStringToByteArray("4d3fe0d71d2abd2828e7a3196ea450d4");

    public String encryptString(char[] password, String plaintext) throws CryptoException {

        byte[] encrypted = null;
        try {
            encrypted = encrypt(password, plaintext.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException //
                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException //
                | InvalidKeySpecException e) {
            throw new CryptoException(e);
        }
        return byteArrayToHexString(encrypted);
    }

    public String decryptString(char[] password, String ciphertext)
            throws CryptoException {

        byte[] ct = hexStringToByteArray(ciphertext);
        byte[] plaintext = null;
        try {
            plaintext = decrypt(password, ct);
        } catch (AEADBadTagException e) {
            throw new CryptoException(e);
        } catch ( //
                NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeySpecException //
                        | InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException //
                        | BadPaddingException e) {
            throw new CryptoException(e);
        }
        return new String(plaintext, StandardCharsets.UTF_8);
    }

    /**
     * Decrypts an AES-GCM encrypted ciphertext and is
     * the reverse operation of {@link AesGcmCryptor#encrypt(char[], byte[])}
     *
     * @param password   passphrase for decryption
     * @param ciphertext encrypted bytes
     * @return plaintext bytes
     * @throws NoSuchPaddingException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws InvalidKeySpecException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws IllegalArgumentException           if the length or format of the ciphertext is bad
     * @throws CryptoException
     */
    public byte[] decrypt(char[] password, byte[] ciphertext)
            throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {

        // input validation
        if (ciphertext == null) {
            throw new IllegalArgumentException("ciphertext cannot be null");
        }

        if (ciphertext.length <= VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH + GCM_TAG_BYTES_LENGTH) {
            throw new IllegalArgumentException("ciphertext too short");
        }

        // the version must match, we don't decrypt other versions
        if (ciphertext[0] != VERSION_BYTE) {
            throw new IllegalArgumentException("wrong version: " + ciphertext[0]);
        }

        // input seems legit, lets decrypt and check integrity

        // derive key from password
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);

        // init cipher
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec params = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8,
                ciphertext,
                VERSION_BYTE_LENGTH,
                GCM_IV_BYTES_LENGTH
        );
        cipher.init(Cipher.DECRYPT_MODE, key, params);

        final int ciphertextOffset = VERSION_BYTE_LENGTH + GCM_IV_BYTES_LENGTH;

        // add version and IV to MAC
        cipher.updateAAD(ciphertext, 0, ciphertextOffset);

        // decipher and check MAC
        return cipher.doFinal(ciphertext, ciphertextOffset, ciphertext.length - ciphertextOffset);
    }

    /**
     * Encrypts a plaintext with a password.
     * <p>
     * The encryption provides the following security properties:
     * Confidentiality + Integrity
     * <p>
     * This is achieved my using the AES-GCM AEAD blockmode with a randomized IV.
     * <p>
     * The tag is calculated over the version byte, the IV as well as the ciphertext.
     * <p>
     * Finally the encrypted bytes have the following structure:
     * <pre>
     *          +-------------------------------------------------------------------+
     *          |         |               |                             |           |
     *          | version | IV bytes      | ciphertext bytes            |    tag    |
     *          |         |               |                             |           |
     *          +-------------------------------------------------------------------+
     * Length:     1B        12B            len(plaintext) bytes            16B
     * </pre>
     * Note: There is no padding required for AES-GCM, but this also implies that
     * the exact plaintext length is revealed.
     *
     * @param password  password to use for encryption
     * @param plaintext plaintext to encrypt
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws InvalidKeyException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     * @throws InvalidKeySpecException
     */
    public byte[] encrypt(char[] password, byte[] plaintext)
            throws NoSuchAlgorithmException, NoSuchPaddingException,
            InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException,
            InvalidKeySpecException {

        // initialise random and generate IV (initialisation vector)
        SecretKey key = deriveAesKey(password, PBKDF2_SALT, AES_KEY_BITS_LENGTH);
        final byte[] iv = new byte[GCM_IV_BYTES_LENGTH];
        SecureRandom random = SecureRandom.getInstanceStrong();
        random.nextBytes(iv);

        // encrypt
        Cipher cipher = Cipher.getInstance(GCM_CRYPTO_NAME);
        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_BYTES_LENGTH * 8, iv);
        cipher.init(Cipher.ENCRYPT_MODE, key, spec);

        // add IV to MAC
        final byte[] versionBytes = new byte[]{VERSION_BYTE};
        cipher.updateAAD(versionBytes);
        cipher.updateAAD(iv);

        // encrypt and MAC plaintext
        byte[] ciphertext = cipher.doFinal(plaintext);

        // prepend VERSION and IV to ciphertext
        byte[] encrypted = new byte[1 + GCM_IV_BYTES_LENGTH + ciphertext.length];
        int pos = 0;
        System.arraycopy(versionBytes, 0, encrypted, 0, VERSION_BYTE_LENGTH);
        pos += VERSION_BYTE_LENGTH;
        System.arraycopy(iv, 0, encrypted, pos, iv.length);
        pos += iv.length;
        System.arraycopy(ciphertext, 0, encrypted, pos, ciphertext.length);

        return encrypted;
    }

    /**
     * We derive a fixed length AES key with uniform entropy from a provided
     * passphrase. This is done with PBKDF2/HMAC256 with a fixed count
     * of iterations and a provided salt.
     *
     * @param password passphrase to derive key from
     * @param salt     salt for PBKDF2 if possible use a per-key salt, alternatively
     *                 a random constant salt is better than no salt.
     * @param keyLen   number of key bits to output
     * @return a SecretKey for AES derived from a passphrase
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    private SecretKey deriveAesKey(char[] password, byte[] salt, int keyLen)
            throws NoSuchAlgorithmException, InvalidKeySpecException {

        if (password == null || salt == null || keyLen <= 0) {
            throw new IllegalArgumentException();
        }
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, PBKDF2_ITERATIONS, keyLen);
        SecretKey pbeKey = factory.generateSecret(spec);

        return new SecretKeySpec(pbeKey.getEncoded(), "AES");
    }

    /**
     * Helper to convert hex strings to bytes.
     * <p>
     * May be used to read bytes from constants.
     */
    private static byte[] hexStringToByteArray(String s) {

        if (s == null) {
            throw new IllegalArgumentException("Provided `null` string.");
        }

        int len = s.length();
        if (len % 2 != 0) {
            throw new IllegalArgumentException("Invalid length: " + len);
        }

        byte[] data = new byte[len / 2];
        for (int i = 0; i < len - 1; i += 2) {
            byte b = (byte) toHexDigit(s, i);
            b <<= 4;
            b |= toHexDigit(s, i + 1);
            data[i / 2] = b;
        }
        return data;
    }

    private static int toHexDigit(String s, int pos) {
        int d = Character.digit(s.charAt(pos), 16);
        if (d < 0) {
            throw new IllegalArgumentException("Cannot parse hex digit: " + s + " at " + pos);
        }
        return d;
    }

    private static String byteArrayToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02X", b));
        }
        return sb.toString();
    }

    public class CryptoException extends Exception {

        public CryptoException(Throwable cause) {
            super(cause);
        }
    }
}

Aquí el proyecto completo con una buena CLI: https://github.com/trichner/tcrypt

Editar: ahora con apropiado encryptStringydecryptString


Esto es increíble. ¡Gracias! Aprendí mucho de su código y después de crear la clase de excepción BadVersionException, su código funcionó perfectamente la primera vez. ¡¡Excelente!!
Morkus

Me gusta este intento Dicho eso ... La sal debe ser aleatoria, no estática. Las iteraciones probablemente tampoco deberían ser estáticas. GCM ya incluye el IV en el cálculo de la etiqueta. Sin embargo, no contiene el número de versión. No debe especificar el proveedor para la portabilidad, el "SunJCE" será el predeterminado en las plataformas que lo admiten. Este código no contiene ningún manejo de cadena de mensaje, que se requiere para esta pregunta en particular .
Maarten Bodewes

encryptStringdecryptString
Muy bien

Esto funcionó muy bien; ty para el código. Cabe señalar que este código requiere API 19 (Kit Kat) o superior para funcionar correctamente.
PGMacDesign

3

Consideraría usar algo como https://www.bouncycastle.org/ Es una biblioteca preconstruida que te permite encriptar lo que quieras con una cantidad de Ciphers diferentes. Entiendo que solo quieres protegerte de espiar, pero si realmente desea proteger la información, el uso de Base64 no lo protegerá realmente.


1
Solo recomendar una biblioteca criptográfica aleatoria con cifrados no es una respuesta a la pregunta. Además de eso, ¿por qué no usar los cifrados integrados?
Maarten Bodewes

2

Aquí hay algunos enlaces en los que puede leer qué admite Java

Cifrar / descifrar un flujo de datos.

Este ejemplo demuestra cómo cifrar (usando un algoritmo de cifrado simétrico como AES, Blowfish, RC2, 3DES, etc.) una gran cantidad de datos. Los datos se pasan en fragmentos a uno de los métodos de cifrado: EncryptBytes, EncryptString, EncryptBytesENC o EncryptStringENC. (El nombre del método indica el tipo de entrada (cadena o matriz de bytes) y el tipo de retorno (cadena codificada o matriz de bytes). Las propiedades FirstChunk y LastChunk se usan para indicar si un fragmento es el primero, el medio o el último en una secuencia para encriptarse. Por defecto, tanto FirstChunk como LastChunk son iguales a verdadero, lo que significa que los datos pasados ​​son la cantidad total.

JCERefGuide

Ejemplos de cifrado de Java


Sí, hay criptografía compatible con Java. El cifrado de una secuencia tampoco es lo que se solicitó.
Maarten Bodewes

2

Como muchos de los chicos ya han dicho, debes usar un cifrador estándar que se usa demasiado como DES o AES.

Un ejemplo simple de cómo puede cifrar y descifrar una cadena en Java utilizando AES .

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

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

public class EncryptorDemo {

    public static String encrypt(String key, String randomVector, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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 text: "  + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key, String randomVector, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(randomVector.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[] originalText = cipher.doFinal(Base64.decodeBase64(encrypted));
            System.out.println("decrypted text: "  + new String(originalText));
            return new String(originalText);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {
        String key = "JavasEncryptDemo"; // 128 bit key
        String randomVector = "RandomJavaVector"; // 16 bytes IV
        decrypt(key, randomVector, encrypt(key, randomVector, "Anything you want to encrypt!"));

    }
}

CBC ya no es un modo seguro. El relleno es vulnerable a los ataques de relleno de Oracle. Además, manejar la clave y los mensajes en String no es seguro. Se quedarán en el grupo de cadenas y aparecerán en un basurero
Saptarshi Basu

2
Agradezco el comentario. Este fue un ejemplo simple de los métodos de cifrado y descifrado de Java que el usuario preguntaba. La pregunta se hizo hace unos 9 años y fue respondida en base a eso. Gracias.
viveknaskar

2
Sí, esto parece una manera simple de introducir cifrar / descifrar. Funcionó como un encanto para mí ... Gracias.
Codewrapper

0

Aquí hay una solución copiar / pegar. También recomiendo leer y votar la respuesta de @ Konstantino a pesar de que no proporciona ningún código. El vector de inicialización (IV) es como una sal: no tiene que mantenerse en secreto. Soy nuevo en GCM y aparentemente AAD es opcional y solo se usa en ciertas circunstancias. Establezca la clave en la variable de entorno SECRET_KEY_BASE. Use algo como KeePass para generar una contraseña de 32 caracteres. Esta solución sigue el modelo de mi solución Ruby.

    public static String encrypt(String s) {
        try {
            byte[] input = s.getBytes("UTF-8");
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            // generate IV
            SecureRandom secureRandom = SecureRandom.getInstanceStrong();
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            secureRandom.nextBytes(ivBytes);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes); // 96 bit tag length
            cipher.init(Cipher.ENCRYPT_MODE, keySpec, gcmSpec);
            // generate AAD
//          byte[] aadBytes = new byte[cipher.getBlockSize()];
//          secureRandom.nextBytes(aadBytes);
//          cipher.updateAAD(aadBytes);
            // encrypt
            byte[] encrypted = cipher.doFinal(input);
            byte[] returnBytes = new byte[ivBytes.length + encrypted.length];
//          byte[] returnBytes = new byte[ivBytes.length + aadBytes.length + encrypted.length];
            System.arraycopy(ivBytes, 0, returnBytes, 0, ivBytes.length);
//          System.arraycopy(aadBytes, 0, returnBytes, ivBytes.length, aadBytes.length);
            System.arraycopy(encrypted, 0, returnBytes, ivBytes.length, encrypted.length);
//          System.arraycopy(encrypted, 0, returnBytes, ivBytes.length+aadBytes.length, encrypted.length);
            String encryptedString = Base64.getEncoder().encodeToString(returnBytes);
            return encryptedString;
        } catch (UnsupportedEncodingException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "encrypt()", "Could not encrypt string: " + e.getMessage());
            return null;
        }
    }

    public static String decrypt(String s) {
        if (s == null || s.length() == 0) return "";
        try {
            byte[] encrypted = Base64.getDecoder().decode(s);
            String keyString = System.getProperty("SECRET_KEY_BASE", System.getenv("SECRET_KEY_BASE"));
            if (keyString == null || keyString.length() == 0) {
                Logger.error(Utils.class, "encrypt()", "$SECRET_KEY_BASE is not set.");
                return null;
            }
            byte[] keyBytes = keyString.getBytes("UTF-8");
            SecretKeySpec keySpec = new SecretKeySpec(keyBytes, "AES");
            Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
            byte[] ivBytes = new byte[cipher.getBlockSize()];
            System.arraycopy(encrypted, 0, ivBytes, 0, ivBytes.length);
            GCMParameterSpec gcmSpec = new GCMParameterSpec(96, ivBytes);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, gcmSpec);
//          cipher.updateAAD(encrypted, ivBytes.length, cipher.getBlockSize());
            byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize(), encrypted.length - cipher.getBlockSize());
//          byte[] decrypted = cipher.doFinal(encrypted, cipher.getBlockSize()*2, encrypted.length - cipher.getBlockSize()*2);
            String decryptedString = new String(decrypted, "UTF-8");
            return decryptedString;
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedEncodingException | InvalidKeyException | 
                InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
            Logger.error(Utils.class, "decrypt()", "Could not decrypt string: " + e.getMessage());
            return null;
        }
    }

Aquí hay un ejemplo:

    String s = "This is a test.";
    String enc = Utils.encrypt(s);
    System.out.println(enc);
    // fQHfYjbD+xAuN5XzH2ojk/EWNeKXUrKRSfx8LU+5dpuKkM/pueCMBjKCZw==
    String dec = Utils.decrypt(enc);
    System.out.println(dec);
    // This is a test.

-4

Es posible que desee considerar alguna herramienta automatizada para hacer la generación del código de cifrado / descifrado, por ejemplo. https://www.stringencrypt.com/java-encryption/

Puede generar diferentes códigos de cifrado y descifrado cada vez para el cifrado de cadenas o archivos.

Es bastante útil cuando se trata de encriptación rápida de cadenas sin usar RSA, AES, etc.

Resultados de muestra:

// encrypted with https://www.stringencrypt.com (v1.1.0) [Java]
// szTest = "Encryption in Java!"
String szTest = "\u9E3F\uA60F\uAE07\uB61B\uBE1F\uC62B\uCE2D\uD611" +
                "\uDE03\uE5FF\uEEED\uF699\uFE3D\u071C\u0ED2\u1692" +
                "\u1E06\u26AE\u2EDC";

for (int iatwS = 0, qUJQG = 0; iatwS < 19; iatwS++)
{
        qUJQG = szTest.charAt(iatwS);
        qUJQG ++;
        qUJQG = ((qUJQG << 5) | ( (qUJQG & 0xFFFF) >> 11)) & 0xFFFF;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 6) | (qUJQG << 10)) & 0xFFFF;
        qUJQG ^= iatwS;
        qUJQG -= iatwS;
        qUJQG = (((qUJQG & 0xFFFF) >> 3) | (qUJQG << 13)) & 0xFFFF;
        qUJQG ^= 0xFFFF;
        qUJQG ^= 0xB6EC;
        qUJQG = ((qUJQG << 8) | ( (qUJQG & 0xFFFF) >> 8)) & 0xFFFF;
        qUJQG --;
        qUJQG = (((qUJQG & 0xFFFF) >> 5) | (qUJQG << 11)) & 0xFFFF;
        qUJQG ++;
        qUJQG ^= 0xFFFF;
        qUJQG += iatwS;
        szTest = szTest.substring(0, iatwS) + (char)(qUJQG & 0xFFFF) + szTest.substring(iatwS + 1);
}

System.out.println(szTest);

Lo usamos todo el tiempo en nuestra empresa.


Esto es seguridad a través de la oscuridad y no es realmente seguro.
Chloe

Esta pregunta está pidiendo un cifrado de fuerza criptográfica moderna como AES, no solo ofuscación para hacer que las cadenas sean más difíciles de extraer estáticamente. Esto ni siquiera parece mantener ningún estado entre los caracteres, por lo que es susceptible al análisis de frecuencia. (Cifrado de sustitución de un solo alfabeto , excepto sobre puntos de código UTF-16 en lugar del alfabeto latino. Pero si lo usa en texto ASCII en inglés, solo obtendrá unos pocos valores únicos de caracteres de 16 bits, a menos que esté leyendo mal)
Peter Cordes

-4
String s1="arshad"; 
char[] s2=s1.toCharArray(); 
int s3= s2.length; 

  System.out.println(s3);
 int i=0; 

// for(int j=0;j<s3;j++) 
// System.out.println(s2[j]); 

for(i=0;i<((s3)/2);i++) { 

char z,f=10; 
z=(char) (s2[i] * f); 
s2[i]=s2[(s3-1)-i]; 
s2[(s3-1)-i]=z; 

String b=new String(s2);

 print(b);  }

Formalmente encripta los datos en un formato ilegible. Para descifrar, use el mismo código. Y cambie s [i] * f por s [I] / f.
Arshad shaik

Esto es seguridad a través de la oscuridad y no es realmente seguro.
Chloe

-5
public static String encryptParams(String myTextInput) {

        String myKey = "40674244454045cb9a70040a30e1c007";
        String myVector = "@1B2c3D4e5F6g7H8";

        String encData = "";

        try{
            JavaEncryprtionUtil encUtil = new JavaEncryprtionUtil();
            encData = Base64.encodeToString(encUtil.encrypt(myTextInput.getBytes("UTF-8"), myKey.getBytes("UTF-8"), myVector.getBytes("UTF-8")),Base64.DEFAULT);
            System.out.println(encData);
        }catch(NoSuchAlgorithmException ex){
            ex.printStackTrace();
        }catch(NoSuchPaddingException ex){
            ex.printStackTrace();
        }catch(InvalidKeyException ex){
            ex.printStackTrace();
        }catch(InvalidAlgorithmParameterException ex){
            ex.printStackTrace();
        }catch(IllegalBlockSizeException ex){
            ex.printStackTrace();
        }catch(BadPaddingException ex){
            ex.printStackTrace();
        }catch(UnsupportedEncodingException ex){
            ex.printStackTrace();
        }

        return encData;
    }

1
¿JavaEncryprtionUtil es parte de la API JDK? si no, debe deletrear el nombre de la biblioteca.
Fermat's Little Student

44
No puedo encontrar esa clase. Parece que la respuesta está hecha.
james.garriss
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.