¿Cómo evitar la instalación de archivos de política JCE "Fuerza ilimitada" al implementar una aplicación?

169

Tengo una aplicación que utiliza cifrado AES de 256 bits que Java no admite de inmediato. Sé que para que esto funcione correctamente, instalo los tarros de fuerza ilimitada JCE en la carpeta de seguridad. Esto está bien para mí como desarrollador, puedo instalarlos.

Mi pregunta es que, dado que esta aplicación se distribuirá, los usuarios finales probablemente no tendrán instalados estos archivos de políticas. Hacer que el usuario final los descargue solo para que la aplicación funcione no es una solución atractiva.

¿Hay alguna manera de hacer que mi aplicación se ejecute sin sobrescribir archivos en la máquina del usuario final? ¿Un software de terceros que pueda manejarlo sin los archivos de política instalados? ¿O una forma de hacer referencia a estos archivos de política desde un JAR?

Duncan Jones
fuente
11
Sospecho que la intención de Sun / Oracle era que el cliente usaría un cifrado menos seguro para que la NSA pueda espiar la conexión. No estoy bromeando o siendo paranoico, pero la criptografía se trata como un arma y hay prohibiciones de exportación para compartir el cifrado .
Trineo

Respuestas:

175

Hay un par de soluciones comúnmente citadas para este problema. Lamentablemente, ninguno de estos es completamente satisfactorio:

  • Instale los archivos de política de fuerza ilimitada . Si bien esta es probablemente la solución adecuada para su estación de trabajo de desarrollo, rápidamente se convierte en una molestia importante (si no un obstáculo) que los usuarios no técnicos instalen los archivos en cada computadora. No hay forma de distribuir los archivos con su programa; deben instalarse en el directorio JRE (que incluso puede ser de solo lectura debido a los permisos).
  • Omita la API de JCE y use otra biblioteca de criptografía como Bouncy Castle . Este enfoque requiere una biblioteca adicional de 1 MB, que puede ser una carga significativa dependiendo de la aplicación. También se siente tonto duplicar la funcionalidad incluida en las bibliotecas estándar. Obviamente, la API también es completamente diferente de la interfaz JCE habitual. (BC implementa un proveedor JCE, pero eso no ayuda porque las restricciones de fuerza clave se aplican antes de entregar la implementación). Esta solución tampoco le permitirá usar conjuntos de cifrado TLS (SSL) de 256 bits, porque Las bibliotecas TLS estándar llaman al JCE internamente para determinar cualquier restricción.

Pero luego está la reflexión. ¿Hay algo que no puedas hacer usando la reflexión?

private static void removeCryptographyRestrictions() {
    if (!isRestrictedCryptography()) {
        logger.fine("Cryptography restrictions removal not needed");
        return;
    }
    try {
        /*
         * Do the following, but with reflection to bypass access checks:
         *
         * JceSecurity.isRestricted = false;
         * JceSecurity.defaultPolicy.perms.clear();
         * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
         */
        final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
        final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
        final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

        final Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
        isRestrictedField.setAccessible(true);
        final Field modifiersField = Field.class.getDeclaredField("modifiers");
        modifiersField.setAccessible(true);
        modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
        isRestrictedField.set(null, false);

        final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
        defaultPolicyField.setAccessible(true);
        final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

        final Field perms = cryptoPermissions.getDeclaredField("perms");
        perms.setAccessible(true);
        ((Map<?, ?>) perms.get(defaultPolicy)).clear();

        final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
        instance.setAccessible(true);
        defaultPolicy.add((Permission) instance.get(null));

        logger.fine("Successfully removed cryptography restrictions");
    } catch (final Exception e) {
        logger.log(Level.WARNING, "Failed to remove cryptography restrictions", e);
    }
}

private static boolean isRestrictedCryptography() {
    // This matches Oracle Java 7 and 8, but not Java 9 or OpenJDK.
    final String name = System.getProperty("java.runtime.name");
    final String ver = System.getProperty("java.version");
    return name != null && name.equals("Java(TM) SE Runtime Environment")
            && ver != null && (ver.startsWith("1.7") || ver.startsWith("1.8"));
}

Simplemente llame removeCryptographyRestrictions() desde un inicializador estático o similar antes de realizar cualquier operación criptográfica.

La JceSecurity.isRestricted = falseparte es todo lo que se necesita para usar cifrados de 256 bits directamente; sin embargo, sin las otras dos operaciones, Cipher.getMaxAllowedKeyLength()seguirá reportando 128, y los conjuntos de cifrado TLS de 256 bits no funcionarán.

Este código funciona en Oracle Java 7 y 8, y omite automáticamente el proceso en Java 9 y OpenJDK donde no es necesario. Al ser un truco feo después de todo, es probable que no funcione en las máquinas virtuales de otros proveedores.

Tampoco funciona en Oracle Java 6, porque las clases privadas de JCE están ofuscadas allí. Sin embargo, la ofuscación no cambia de una versión a otra, por lo que técnicamente es posible admitir Java 6.

ntoskrnl
fuente
23
La solución de reflexión puede violar el Acuerdo de licencia de Java : "F. RESTRICCIONES DE TECNOLOGÍA JAVA. No puede ... cambiar el comportamiento de ... clases, interfaces o subpaquetes que de alguna manera se identifiquen como 'java', 'javax' , 'sol', 'oráculo' o convención similar ... "
M. Dudley
14
@ M.Dudley podría ser. Consulte con un abogado antes de enviar un producto que contenga este código si le concierne.
ntoskrnl
3
@peabody Incluir un JRE de 100 MB con su programa es ciertamente una opción en algunos casos. Pero si no, los usuarios aún tendrán que instalar los archivos de política manualmente, incluso si los incluye con su programa (debido a varias razones, como los permisos de archivos). En mi experiencia, muchos usuarios no son capaces de eso.
ntoskrnl
8
Parece que la solución de reflexión dejó de funcionar en 1.8.0_112. Funciona en 1.8.0_111, pero no en 112.
John L
3
@JohnL Yo uso esto en una aplicación. Después de tener problemas con el finalcampo en 8u111, lo modifiqué para que pueda cambiar el campo final, siguiendo esta respuesta . El resultado es casi el mismo que la nueva versión de ntoskrnl, excepto que no lo declare modifiersFieldcomo final. Uno de mis usuarios informa que también funciona en 8u112.
Arjan
87

Esto ya no es necesario para Java 9 , ni para ninguna versión reciente de Java 6, 7 u 8. ¡Finalmente! :)

Según JDK-8170157 , la política criptográfica ilimitada ahora está habilitada de forma predeterminada.

Versiones específicas del tema JIRA:

  • Java 9 (10, 11, etc.): ¡Cualquier lanzamiento oficial!
  • Java 8u161 o posterior (disponible ahora )
  • Java 7u171 o posterior (solo disponible a través de 'My Oracle Support')
  • Java 6u181 o posterior (solo disponible a través de 'My Oracle Support')

Tenga en cuenta que si por alguna extraña razón se necesita el comportamiento anterior en Java 9, se puede configurar usando:

Security.setProperty("crypto.policy", "limited");
cranphin
fuente
44
De hecho, esta política es la predeterminada, por lo que no es necesario realizar acciones en Java 9.
ntoskrnl
A partir del 14/01/2018 (el último Oracle JDK es 8u151 / 152) esto todavía no está habilitado de manera predeterminada en Java 8, mucho más de un año después de que esta respuesta se escribió originalmente ... Sin embargo, de acuerdo con java.com/en/jre -jdk-cryptoroadmap.html esto está destinado a GA el 16/01/2018
Alex
En mi caso, y para obtener una Marca de A en este sitio: ssllabs.com/ssltest ... Tengo que configurarlo de esta manera: Security.setProperty ("crypto.policy", "unlimited"); luego ... configure server.ssl.ciphers en mis aplicaciones.propiedades con algoritmos basados ​​en 256 indicados en este artículo -> weakdh.org/sysadmin.html
Artanis Zeratul
También relevante para OpenJDK 8-Instalaciones. Ver: stackoverlow-Article: ¿La política de JCE está incluida en openjdk 8?
Leole
22

Aquí está la solución: http://middlesphere-1.blogspot.ru/2014/06/this-code-allows-to-break-limit-if.html

//this code allows to break limit if client jdk/jre has no unlimited policy files for JCE.
//it should be run once. So this static section is always execute during the class loading process.
//this code is useful when working with Bouncycastle library.
static {
    try {
        Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
        field.setAccessible(true);
        field.set(null, java.lang.Boolean.FALSE);
    } catch (Exception ex) {
    }
}
Miguel
fuente
Esta es la misma solución que la mía, excepto sin la parte "defaultPolicy". La publicación del blog está fechada después de mi respuesta.
ntoskrnl
1
¿Pero es esto lo correcto? En tiempo real, ¿puede este código desafiar la seguridad de la aplicación? No estoy seguro, por favor, ayúdame a entender su impacto.
Plato
1
Recibo este error después de ejecutar esto:java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
Andy
3
A partir de Java 8 build 111, esta solución será insuficiente, ya que el isRestrictedcampo se ha convertido en final ( bugs.openjdk.java.net/browse/JDK-8149417 ). La respuesta de @ ntoskrnl se encarga de cualquier posible inclusión de un modificador "final". El comentario de @ M.Dudley sobre el Acuerdo de licencia de Java todavía se aplica también.
MPelletier
13

A partir de JDK 8u102, las soluciones publicadas que dependen de la reflexión ya no funcionarán: el campo que estas soluciones establecen ahora final( https://bugs.openjdk.java.net/browse/JDK-8149417 ).

Parece que ha vuelto a (a) usar Bouncy Castle o (b) instalar los archivos de política JCE.

Sam Roberton
fuente
77
Siempre puede usar más reflexión stackoverflow.com/questions/3301635/…
Universal Electricity
Sí, la solución de @ M.Dudley seguirá funcionando para el isRestrictedcampo, ya que se encarga de una posible adición de un modificador "final".
MPelletier
1
Nueva versión JDK 8u151 tiene "Nueva propiedad de seguridad para controlar la política de cifrado". En pocas palabras: elimine el "#" de la línea "# crypto.policy = unlimited" en "lib \ security \ java.security": oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html
hemisphire
8

Para una biblioteca de criptografía alternativa, eche un vistazo a Bouncy Castle . Tiene AES y mucha funcionalidad adicional. Es una biblioteca liberal de código abierto. Sin embargo, tendrá que usar la API ligera y patentada de Bouncy Castle para que esto funcione.

Maarten Bodewes
fuente
19
Son un excelente proveedor de cifrado, pero aún requieren el archivo JCE de fuerza ilimitada para trabajar con claves grandes.
John Meagher
16
Si usa la API de Bouncy Castle directamente, no necesita los archivos de fuerza ilimitados.
laz
4

Podrías usar el método

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

para probar la longitud de la clave disponible, úsela e informe al usuario sobre lo que está sucediendo. Algo que indica que su aplicación está volviendo a las claves de 128 bits debido a que los archivos de políticas no se están instalando, por ejemplo. Los usuarios conscientes de la seguridad instalarán los archivos de políticas, otros continuarán usando claves más débiles.

Christian Schulte
fuente
3

Para nuestra aplicación, teníamos una arquitectura de servidor cliente y solo permitíamos descifrar / cifrar datos en el nivel del servidor. Por lo tanto, los archivos JCE solo se necesitan allí.

Tuvimos otro problema en el que necesitábamos actualizar un jar de seguridad en las máquinas cliente, a través de JNLP, sobrescribe las bibliotecas en${java.home}/lib/security/ y la JVM en la primera ejecución.

Eso lo hizo funcionar.

Mohamed Mansour
fuente
2

Aquí hay una versión actualizada de la respuesta ntoskrnl . Además contiene una función para eliminar el modificador final como Arjan mencionado en los comentarios.

Esta versión funciona con JRE 8u111 o posterior.

private static void removeCryptographyRestrictions() {
    if (!isRestrictedCryptography()) {
        return;
    }
    try {
        /*
         * Do the following, but with reflection to bypass access checks:
         * 
         * JceSecurity.isRestricted = false; JceSecurity.defaultPolicy.perms.clear();
         * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
         */
        final Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
        final Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
        final Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

        Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
        isRestrictedField.setAccessible(true);
        setFinalStatic(isRestrictedField, true);
        isRestrictedField.set(null, false);

        final Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
        defaultPolicyField.setAccessible(true);
        final PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

        final Field perms = cryptoPermissions.getDeclaredField("perms");
        perms.setAccessible(true);
        ((Map<?, ?>) perms.get(defaultPolicy)).clear();

        final Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
        instance.setAccessible(true);
        defaultPolicy.add((Permission) instance.get(null));
    }
    catch (final Exception e) {
        e.printStackTrace();
    }
}

static void setFinalStatic(Field field, Object newValue) throws Exception {
      field.setAccessible(true);

      Field modifiersField = Field.class.getDeclaredField("modifiers");
      modifiersField.setAccessible(true);
      modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

      field.set(null, newValue);
   }

private static boolean isRestrictedCryptography() {
    // This simply matches the Oracle JRE, but not OpenJDK.
    return "Java(TM) SE Runtime Environment".equals(System.getProperty("java.runtime.name"));
}
xoned
fuente
Funciona bien, pero la línea ((Map<?, ?>) perms.get(defaultPolicy)).clear();produce un error de compilación. Comentar no parece afectar su funcionalidad. ¿Es necesaria esta línea?
Andreas Unterweger
2

Aquí hay una versión modificada del código de @ ntoskrnl con isRestrictedCryptographyverificación por registro realCipher.getMaxAllowedKeyLength , slf4j y soporte de inicialización singleton desde el arranque de la aplicación de esta manera:

static {
    UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

Este código detendrá correctamente la reflexión cuando la política ilimitada esté disponible por defecto en Java 8u162 como predice la respuesta de @ cranphin.


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Map;

// /programming/1179672/how-to-avoid-installing-unlimited-strength-jce-policy-files-when-deploying-an
public class UnlimitedKeyStrengthJurisdictionPolicy {

    private static final Logger log = LoggerFactory.getLogger(UnlimitedKeyStrengthJurisdictionPolicy.class);

    private static boolean isRestrictedCryptography() throws NoSuchAlgorithmException {
        return Cipher.getMaxAllowedKeyLength("AES/ECB/NoPadding") <= 128;
    }

    private static void removeCryptographyRestrictions() {
        try {
            if (!isRestrictedCryptography()) {
                log.debug("Cryptography restrictions removal not needed");
                return;
            }
            /*
             * Do the following, but with reflection to bypass access checks:
             *
             * JceSecurity.isRestricted = false;
             * JceSecurity.defaultPolicy.perms.clear();
             * JceSecurity.defaultPolicy.add(CryptoAllPermission.INSTANCE);
             */
            Class<?> jceSecurity = Class.forName("javax.crypto.JceSecurity");
            Class<?> cryptoPermissions = Class.forName("javax.crypto.CryptoPermissions");
            Class<?> cryptoAllPermission = Class.forName("javax.crypto.CryptoAllPermission");

            Field isRestrictedField = jceSecurity.getDeclaredField("isRestricted");
            isRestrictedField.setAccessible(true);
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            modifiersField.setInt(isRestrictedField, isRestrictedField.getModifiers() & ~Modifier.FINAL);
            isRestrictedField.set(null, false);

            Field defaultPolicyField = jceSecurity.getDeclaredField("defaultPolicy");
            defaultPolicyField.setAccessible(true);
            PermissionCollection defaultPolicy = (PermissionCollection) defaultPolicyField.get(null);

            Field perms = cryptoPermissions.getDeclaredField("perms");
            perms.setAccessible(true);
            ((Map<?, ?>) perms.get(defaultPolicy)).clear();

            Field instance = cryptoAllPermission.getDeclaredField("INSTANCE");
            instance.setAccessible(true);
            defaultPolicy.add((Permission) instance.get(null));

            log.info("Successfully removed cryptography restrictions");
        } catch (Exception e) {
            log.warn("Failed to remove cryptography restrictions", e);
        }
    }

    static {
        removeCryptographyRestrictions();
    }

    public static void ensure() {
        // just force loading of this class
    }
}
Vadzim
fuente
-1

Durante la instalación de su programa, solo solicite al usuario y descargue un script de lote de DOS o un script de shell de Bash y copie el JCE en la ubicación correcta del sistema.

Solía ​​tener que hacer esto para un servicio web de servidor y, en lugar de un instalador formal, solo proporcioné scripts para configurar la aplicación antes de que el usuario pudiera ejecutarla. Puede hacer que la aplicación no se pueda ejecutar hasta que ejecuten el script de configuración. ¿También puede hacer que la aplicación se queje de que falta el JCE y luego solicitar descargar y reiniciar la aplicación?

djangofan
fuente
77
"hacer que mi aplicación se ejecute sin sobrescribir archivos en la máquina del usuario final"
erickson
Hice una edición completa de mi respuesta ya que mi respuesta inicial fue incorrecta.
djangofan