¿Por qué Java permite el cifrado AES-256 bit en sistemas sin políticas de fuerza ilimitada de JCE si se usa PBE?

7

Es un conocimiento bastante estándar que, debido a los controles de exportación de criptografía, Oracle JRE se envía con la fuerza criptográfica "limitada" habilitada como se indica en Documentación JCA . Para AES, el max predeterminado es la longitud de la clave 128 bit . Para habilitar el cifrado 192 bit o 256 bit , JCE archivos de política de jurisdicción de jurisdicción ilimitada debe estar instalado en el JRE.

Hace poco me encontré con una situación por accidente que me llevó a creer que hay un problema con esta aplicación. No estoy seguro de llamarlo error, pero definitivamente no está bien documentado (o al menos no puedo encontrar nada que lo documente).

La verificación de la longitud de la clave se realiza dentro de cipher.init() , y creo que usa Cipher.getMaxAllowedKeyLength("AES") para determinar si el tamaño máximo de la clave es 128 o Integer.MAX_VALUE .

Al utilizar el cifrado con clave normal, esta comprobación está bien. En una instalación de JRE predeterminada, el código siguiente se ejecuta como se esperaba (estoy usando Groovy para la prueba, pero también he intentado esto en Java puro):

static boolean isUnlimitedStrengthCrypto() {
    Cipher.getMaxAllowedKeyLength("AES") > 128
}

@Test
public void testShouldEncryptAndDecryptWith128BitKey() throws Exception {
    // Arrange
    MessageDigest sha1 = MessageDigest.getInstance("SHA1")
    String key = Hex.encodeHexString(sha1.digest("thisIsABadPassword".getBytes()))[0..<32]
    String iv = Hex.encodeHexString(sha1.digest("thisIsABadIv".getBytes()))[0..<32]

    logger.info("Key: ${key}")
    logger.info("IV : ${iv}")

    SecretKey secretKey = new SecretKeySpec(Hex.decodeHex(key.toCharArray()), "AES")
    IvParameterSpec ivParameterSpec = new IvParameterSpec(Hex.decodeHex(iv.toCharArray()))

    // Act    
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)

    String message = "This is a plaintext message."

    byte[] cipherBytes = cipher.doFinal(message.getBytes())

    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec)

    byte[] recoveredBytes = cipher.doFinal(cipherBytes)

    String recovered = new String(recoveredBytes)
    System.out.println("Recovered message: " + recovered)

    // Assert
    assert recovered == message
}

Esto genera la salida:

[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - Key: 6d71f677ecb99cf623246fb48a1d8130
[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - IV : 912ed675905eb4cb0f9f5714c9c9ec39

Y esta prueba:

@Test
public void testShouldNotEncryptAndDecryptWith256BitKey() throws Exception {
    // Arrange
    Assume.assumeTrue("This test should only run when unlimited (256 bit) encryption is not available", !isUnlimitedStrengthCrypto())

    MessageDigest sha1 = MessageDigest.getInstance("SHA1")
    String key = Hex.encodeHexString(sha1.digest("thisIsABadPassword".getBytes()))[0..<32] * 2
    String iv = Hex.encodeHexString(sha1.digest("thisIsABadIv".getBytes()))[0..<32]

    logger.info("Key: ${key}")
    logger.info("IV : ${iv}")

    SecretKey secretKey = new SecretKeySpec(Hex.decodeHex(key.toCharArray()), "AES")
    IvParameterSpec ivParameterSpec = new IvParameterSpec(Hex.decodeHex(iv.toCharArray()))

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")

    // Act
    def msg = shouldFail(InvalidKeyException) {
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivParameterSpec)
    }

    // Assert
    assert msg =~ "Illegal key size"
}

Genera esta salida:

[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - Key: 6d71f677ecb99cf623246fb48a1d81306d71f677ecb99cf623246fb48a1d8130
[main] INFO  *.crypto.OpenSSLPBEEncryptorTest  - IV : 912ed675905eb4cb0f9f5714c9c9ec39

Y pasa con éxito lanzando la excepción.

El problema surge cuando se utiliza el cifrado basado en contraseña.

Debido a que la derivación de la clave de la contraseña (y sal, si se proporciona), ocurre durante cipher.init() pero después de la verificación de la longitud de la clave, la verificación de la longitud en realidad se aplica a la representación byte[] de% código%. Esto significa que si se usa una contraseña < = 16 caracteres ( SecretKey.getEncoded() ), la verificación pasará incluso si el cifrado especificado usa una clave 16 bytes / 128 bits . La clave derivada será 256 bit aunque la política de jurisdicción lo prohíba. A la inversa, si una contraseña > Se utilizan 16 caracteres, incluso con un cifrado 256 bits , la verificación de longitud fallará y se lanzará un 128 bit . El siguiente código demuestra esto:

@Test
public void testShouldEncryptAndDecryptWithPBEShortPassword() throws Exception {
    // Arrange
    final String PASSWORD = "password"
    String salt = "saltsalt"

    logger.info("Password: ${PASSWORD}")
    logger.info("Salt    : ${salt}")

    String algorithm;
    algorithm = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"
    PBEKeySpec pbeSpec = new PBEKeySpec(PASSWORD.toCharArray());
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, "BC");
    SecretKey secretKey = secretKeyFactory.generateSecret(pbeSpec);
    PBEParameterSpec saltParams = new PBEParameterSpec(salt.getBytes("US-ASCII"), 0);

    // Act
    Cipher cipher = Cipher.getInstance(algorithm, "BC");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey, saltParams);

    String message = "This is a plaintext message."

    byte[] cipherBytes = cipher.doFinal(message.getBytes())

    cipher.init(Cipher.DECRYPT_MODE, secretKey, saltParams)

    byte[] recoveredBytes = cipher.doFinal(cipherBytes)

    String recovered = new String(recoveredBytes)
    System.out.println("Recovered message: " + recovered)

    // Assert
    assert recovered == message
}

@Test
public void testShouldNotEncryptAndDecryptWithPBELongPassword() throws Exception {
    // Arrange
    Assume.assumeTrue("This test should only run when unlimited (256 bit) encryption is not available", !isUnlimitedStrengthCrypto())

    final String PASSWORD = "thisIsABadPassword"
    String salt = "saltsalt"

    logger.info("Password: ${PASSWORD}")
    logger.info("Salt    : ${salt}")

    String algorithm;
    algorithm = "PBEWITHMD5AND256BITAES-CBC-OPENSSL"
    PBEKeySpec pbeSpec = new PBEKeySpec(PASSWORD.toCharArray());
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, "BC");
    SecretKey secretKey = secretKeyFactory.generateSecret(pbeSpec);
    PBEParameterSpec saltParams = new PBEParameterSpec(salt.getBytes("US-ASCII"), 0);

    Cipher cipher = Cipher.getInstance(algorithm, "BC");

    // Act
    def msg = shouldFail(InvalidKeyException) {
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, saltParams);
    }

    // Assert
    assert msg =~ "Illegal key size"
}

Ambas pruebas "pasan" en eso en un sistema con criptografía de fuerza "limitada", el cifrado InvalidKeyException todavía está disponible si la contraseña es lo suficientemente corta. A la inversa, esta prueba demuestra que una contraseña larga causa una excepción incluso cuando se utiliza el cifrado 256 bit :

@Test
public void testShouldNotEncryptAndDecryptWithPBELongPasswordEvenWith128BitKey() throws Exception {
    // Arrange
    Assume.assumeTrue("This test should only run when unlimited (256 bit) encryption is not available", !isUnlimitedStrengthCrypto())

    final String PASSWORD = "thisIsABadPassword"
    String salt = "saltsalt"

    logger.info("Password: ${PASSWORD}")
    logger.info("Salt    : ${salt}")

    String algorithm;
    algorithm = "PBEWITHMD5AND128BITAES-CBC-OPENSSL"
    PBEKeySpec pbeSpec = new PBEKeySpec(PASSWORD.toCharArray());
    SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(algorithm, "BC");
    SecretKey secretKey = secretKeyFactory.generateSecret(pbeSpec);
    PBEParameterSpec saltParams = new PBEParameterSpec(salt.getBytes("US-ASCII"), 0);

    Cipher cipher = Cipher.getInstance(algorithm, "BC");

    // Act
    def msg = shouldFail(InvalidKeyException) {
        cipher.init(Cipher.ENCRYPT_MODE, secretKey, saltParams);
    }

    // Assert
    assert msg =~ "Illegal key size"
}

Pensé que podría ser un falso positivo, así que utilicé OpenSSL para cifrar archivos usando el cifrado 128 bit y 128 con contraseñas "largas" y "cortas" y traté de descifrarlos con Java. Los resultados son de un sistema con criptografía de fuerza "limitada":

$ openssl enc -aes-128-cbc -e -in plain.txt -out salted_raw_128_long.enc -k thisIsABadPassword -p
$ openssl enc -aes-128-cbc -e -in plain.txt -out salted_raw_128_short.enc -k password -p
$ openssl enc -aes-256-cbc -e -in plain.txt -out salted_raw_256_long.enc -k thisIsABadPassword -p
$ openssl enc -aes-256-cbc -e -in plain.txt -out salted_raw_256_short.enc -k password -p


Cipher  | Password length | Should Work | Does Work
--------|-----------------|-------------|-----------
AES-128 |   <= 16 chars   |     YES     |    YES
AES-128 |    > 16 chars   |     YES     |     NO
AES-256 |   <= 16 chars   |      NO     |    YES
AES-256 |    > 16 chars   |      NO     |     NO

Tengo algunas preguntas:

  1. ¿Alguien más puede reproducir este comportamiento?
  2. ¿Es este un comportamiento intencionado o un error?
  3. Si está destinado, ¿está suficientemente documentado en alguna parte?

Actualizar Después de investigar más en una máquina sin las políticas de jurisdicción de fuerza ilimitada instaladas, he determinado estas longitudes máximas de contraseña para los siguientes algoritmos PBE:

Algorithm        |        Max Password Length
---------------------------------------------
PBEWITHMD5AND128BITAES-CBC-OPENSSL |    16
PBEWITHMD5AND192BITAES-CBC-OPENSSL |    16
PBEWITHMD5AND256BITAES-CBC-OPENSSL |    16
PBEWITHMD5ANDDES                   |    16
PBEWITHMD5ANDRC2                   |    16
PBEWITHSHA1ANDRC2                  |    16
PBEWITHSHA1ANDDES                  |    16
PBEWITHSHAAND128BITAES-CBC-BC      |     7
PBEWITHSHAAND192BITAES-CBC-BC      |     7
PBEWITHSHAAND256BITAES-CBC-BC      |     7
PBEWITHSHAAND40BITRC2-CBC          |     7
PBEWITHSHAAND128BITRC2-CBC         |     7
PBEWITHSHAAND40BITRC4              |     7
PBEWITHSHAAND128BITRC4             |     7
PBEWITHSHA256AND128BITAES-CBC-BC   |     7
PBEWITHSHA256AND192BITAES-CBC-BC   |     7
PBEWITHSHA256AND256BITAES-CBC-BC   |     7
PBEWITHSHAAND2-KEYTRIPLEDES-CBC    |     7
PBEWITHSHAAND3-KEYTRIPLEDES-CBC    |     7
PBEWITHSHAANDTWOFISH-CBC           |     7
    
pregunta Andy 05.12.2015 - 01:12
fuente

1 respuesta

1

Derecho internacional

El marco de JCA no está diseñado para hacer cumplir inequívocamente el derecho internacional, sin embargo, las diferencias arbitrarias entre la ley y el software deben corregirse para facilitar el cumplimiento. La autenticación mutua garantiza el manejo adecuado de las restricciones de resistencia solo si el proveedor lo maneja correctamente.

Si hay una infracción de cumplimiento, los descargos de responsabilidad de los proveedores de Oracle y los proveedores dejarán de culpar a quienes integran estos productos en los sistemas.

Esta encuesta de Regulación de la criptografía internacional y la economía mundial de la información es un buen comienzo para comprender cuáles son las responsabilidades del CTO, el especialista en seguridad y el arquitecto con respecto a las limitaciones criptográficas en un mercado internacional.

U.S. Ley

La Oficina de Industria y Política de Seguridad del Departamento de Comercio de EE. UU. se define en una Conjunto de documentos algo complicados. La sección pertinente para la criptografía informática se llama Categoría 5 y está documentada here .

Documentación JCA

Estos son los documentos de Oracle relativos al marco JCA, sin embargo, el marco espera la cooperación de los proveedores.

Documentación del proveedor de criptografía y pruebas de fuerza bruta

Cada proveedor puede o no tener su propia documentación. No estaba muy impresionado con la documentación de BountyCastle el año pasado cuando la leía para obtener descripciones claras de los límites y las opciones. No sería sorprendente que la ejecución de pruebas con permutaciones clave de algoritmos y archivos de políticas sea la única forma de verificar de forma segura el cumplimiento de la legislación internacional.

La pregunta en realidad tiene más información sobre los resultados de las pruebas reales de lo que he visto en ningún otro lugar. (Buen trabajo.)

La definición de Shannon de un bit

Tenga en cuenta que la mayoría de las restricciones se miden en bits, que deberían ser. La criptografía es en gran medida un juego probabilístico, y la definición de Shannon de un bit es una relación de probabilidad de 2: 1. Esto se aplica directamente a las contraseñas, si se desea ser preciso. Si los proveedores de criptografía aplican correctamente las matemáticas es dudoso.

El número de bits en un carácter depende del rango de la codificación. Un dígito hexadecimal es log 2 (16) = 4 bits. Un carácter imprimible ASCII tiene 95 valores posibles, por lo que en realidad es log 2 (95) = 6.57 bits (no un byte). Los caracteres imprimibles en UTF-8 superan considerablemente los 8 bits.

Si los legisladores entienden que las matemáticas también son cuestionables.

    
respondido por el Douglas Daseeco 11.02.2017 - 09:05
fuente

Lea otras preguntas en las etiquetas