Comment éviter d'installer des fichiers de stratégie JCE «Unlimited Strength» lors du déploiement d'une application?

169

J'ai une application qui utilise le cryptage AES 256 bits qui n'est pas pris en charge par Java prêt à l'emploi. Je sais que pour que cela fonctionne correctement, j'installe les pots JCE de force illimitée dans le dossier de sécurité. Cela me convient en tant que développeur, je peux les installer.

Ma question est que, puisque cette application sera distribuée, les utilisateurs finaux n'auront probablement pas ces fichiers de stratégie installés. Demander à l'utilisateur final de les télécharger uniquement pour faire fonctionner l'application n'est pas une solution attrayante.

Existe-t-il un moyen de faire fonctionner mon application sans écraser les fichiers sur la machine de l'utilisateur final? Un logiciel tiers capable de le gérer sans que les fichiers de stratégie ne soient installés? Ou un moyen de simplement référencer ces fichiers de stratégie à partir d'un JAR?

Duncan Jones
la source
11
Je soupçonne que l'intention de Sun / Oracle était que le client utilise un chiffrement moins sûr afin que la NSA puisse espionner la connexion. Je ne plaisante pas ni ne suis paranoïaque, mais la cryptographie est traitée comme une arme et il y a des interdictions d'exportation sur le partage du cryptage .
Traîneau

Réponses:

175

Il existe quelques solutions couramment citées à ce problème. Malheureusement, aucun de ces éléments n'est entièrement satisfaisant:

  • Installez les fichiers de stratégie de force illimitée . Bien que ce soit probablement la bonne solution pour votre poste de travail de développement, il devient rapidement un problème majeur (sinon un obstacle) que des utilisateurs non techniques installent les fichiers sur chaque ordinateur. Il n'y a aucun moyen de distribuer les fichiers avec votre programme; ils doivent être installés dans le répertoire JRE (qui peut même être en lecture seule en raison des autorisations).
  • Ignorez l'API JCE et utilisez une autre bibliothèque de cryptographie telle que Bouncy Castle . Cette approche nécessite une bibliothèque supplémentaire de 1 Mo, ce qui peut représenter une charge importante en fonction de l'application. Il semble également ridicule de dupliquer les fonctionnalités incluses dans les bibliothèques standard. De toute évidence, l'API est également complètement différente de l'interface JCE habituelle. (BC implémente un fournisseur JCE, mais cela n'aide pas car les restrictions de force de clé sont appliquées avant de passer à l'implémentation.) Cette solution ne vous permettra pas non plus d'utiliser des suites de chiffrement TLS (SSL) 256 bits, car le Les bibliothèques TLS standard appellent le JCE en interne pour déterminer les restrictions.

Mais ensuite, il y a la réflexion. Y a-t-il quelque chose que vous ne pouvez pas faire en utilisant la réflexion?

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"));
}

Appelez simplement à removeCryptographyRestrictions()partir d'un initialiseur statique ou autre avant d'effectuer des opérations cryptographiques.

La JceSecurity.isRestricted = falsepartie est tout ce qui est nécessaire pour utiliser directement les chiffrements 256 bits; cependant, sans les deux autres opérations, Cipher.getMaxAllowedKeyLength()continuera à rapporter 128, et les suites de chiffrement TLS 256 bits ne fonctionneront pas.

Ce code fonctionne sur Oracle Java 7 et 8 et ignore automatiquement le processus sur Java 9 et OpenJDK là où il n'est pas nécessaire. Étant un hack laid après tout, cela ne fonctionne probablement pas sur les VM d'autres fournisseurs.

Cela ne fonctionne pas non plus sur Oracle Java 6, car les classes JCE privées y sont masquées. L'obscurcissement ne change pas d'une version à l'autre, il est donc toujours techniquement possible de prendre en charge Java 6.

ntoskrnl
la source
23
La solution de réflexion peut violer le contrat de licence Java : "F. RESTRICTIONS DE LA TECHNOLOGIE JAVA. Vous ne pouvez pas ... modifier le comportement de ... classes, interfaces ou sous-packages identifiés de quelque manière que ce soit comme 'java', 'javax' , 'sun', 'oracle' or similar convention ... "
M. Dudley
14
@ M.Dudley pourrait être. Vérifiez auprès d'un avocat avant d'expédier un produit contenant ce morceau de code si cela vous concerne.
ntoskrnl
3
@peabody Inclure un JRE de 100 Mo avec votre programme est certainement une option dans certains cas. Mais sinon, les utilisateurs devront toujours installer les fichiers de stratégie manuellement, même si vous les incluez avec votre programme (pour diverses raisons telles que les autorisations de fichiers). D'après mon expérience, de nombreux utilisateurs ne sont pas capables de cela.
ntoskrnl
8
Il semble que la solution de réflexion a cessé de fonctionner dans 1.8.0_112. Cela fonctionne en 1.8.0_111, mais pas en 112.
John L
3
@JohnL J'utilise ceci dans une application. Après avoir rencontré des problèmes avec le finalchamp dans 8u111, je l'ai modifié pour qu'il puisse changer le champ final, à la suite de cette réponse . Le résultat est à peu près le même que celui de la nouvelle version de ntoskrnl, sauf que je n'ai pas déclaré modifiersFieldcomme final. Un de mes utilisateurs rapporte que cela fonctionne également dans 8u112.
Arjan
87

Ce n'est plus nécessaire pour Java 9 , ni pour aucune version récente de Java 6, 7 ou 8. Enfin! :)

Par JDK-8170157 , la politique cryptographique illimitée est désormais activée par défaut.

Versions spécifiques du problème JIRA:

  • Java 9 (10, 11, etc.): Toute version officielle!
  • Java 8u161 ou version ultérieure (disponible maintenant )
  • Java 7u171 ou version ultérieure (uniquement disponible via 'My Oracle Support')
  • Java 6u181 ou version ultérieure (uniquement disponible via 'My Oracle Support')

Notez que si, pour une raison étrange, l'ancien comportement est nécessaire dans Java 9, il peut être défini en utilisant:

Security.setProperty("crypto.policy", "limited");
cranphin
la source
4
En fait, cette politique est la politique par défaut, donc aucune action n'est nécessaire dans Java 9!
ntoskrnl
À partir du 2018/01/14 (le dernier JDK Oracle est 8u151 / 152), cela n'est toujours pas activé par défaut sur Java 8, bien plus d'un an après que cette réponse a été écrite à l'origine ... Cependant, selon java.com/en/jre -jdk-cryptoroadmap.html ceci est destiné à GA le 16/01/2018
Alex le
Dans mon cas, et pour que j'obtienne une marque de A sur ce site: ssllabs.com/ssltest ... je dois le configurer de cette façon: Security.setProperty ("crypto.policy", "unlimited"); puis ... définissez server.ssl.ciphers dans mes applications.properties avec des algorithmes basés sur 256 indiqués dans cet article -> lowdh.org/sysadmin.html
Artanis Zeratul
Également pertinent pour les installations OpenJDK 8. Voir: stackoverlow-Article: La stratégie JCE est-elle fournie avec openjdk 8?
leole
22

Voici la solution: 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) {
    }
}
Mike
la source
C'est la même solution que la mienne, sauf sans la partie "defaultPolicy". Le billet de blog est daté après ma réponse.
ntoskrnl
1
Mais est-ce la bonne chose à faire? En temps réel, ce code peut-il remettre en cause la sécurité de l'application? Je ne suis pas sûr de bien vouloir m'aider à comprendre son impact.
Plat du
1
java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
Andy
3
A partir de Java 8 build 111, cette solution sera insuffisante, car le isRestrictedchamp est devenu définitif ( bugs.openjdk.java.net/browse/JDK-8149417 ). La réponse de @ ntoskrnl prend en charge toute inclusion possible d'un modificateur "final". Le commentaire de @ M.Dudley sur le contrat de licence Java s'applique toujours aussi.
MPelletier
13

À partir du JDK 8u102, les solutions publiées reposant sur la réflexion ne fonctionneront plus: le champ que ces solutions définissent est maintenant final( https://bugs.openjdk.java.net/browse/JDK-8149417 ).

On dirait que c'est de retour soit (a) à l'aide de Bouncy Castle, soit (b) à l'installation des fichiers de stratégie JCE.

Sam Roberton
la source
7
Vous pouvez toujours utiliser plus de réflexion stackoverflow.com/questions/3301635/…
Universal Electricity
Oui, la solution de @ M.Dudley fonctionnera toujours pour le isRestrictedterrain, car elle prend en charge l'ajout éventuel d'un modificateur "final".
MPelletier
1
La nouvelle version JDK 8u151 a "Nouvelle propriété de sécurité pour contrôler la politique de cryptage". En bout de ligne: supprimez le "#" de la ligne "# crypto.policy = unlimited" dans "lib \ security \ java.security": oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html
hemisphire
8

Pour une bibliothèque de cryptographie alternative, jetez un œil à Bouncy Castle . Il a AES et de nombreuses fonctionnalités supplémentaires. C'est une bibliothèque open source libérale. Vous devrez cependant utiliser l'API Bouncy Castle légère et propriétaire pour que cela fonctionne.

Maarten Bodewes
la source
19
Ils sont un excellent fournisseur de crypto, mais nécessitent toujours le fichier JCE de force illimitée pour fonctionner avec de grandes clés.
John Meagher
16
Si vous utilisez directement l'API Bouncy Castle, vous n'avez pas besoin des fichiers de force illimités.
laz
4

Vous pouvez utiliser la méthode

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

pour tester la longueur de clé disponible, utilisez-la et informez l'utilisateur de ce qui se passe. Quelque chose indiquant que votre application retombe aux clés de 128 bits en raison, par exemple, des fichiers de stratégie non installés. Les utilisateurs soucieux de la sécurité installeront les fichiers de stratégie, d'autres continueront à utiliser des clés plus faibles.

Christian Schulte
la source
3

Pour notre application, nous avions une architecture client-serveur et nous n'autorisions le déchiffrement / chiffrement des données qu'au niveau du serveur. Par conséquent, les fichiers JCE ne sont nécessaires que là-bas.

Nous avons eu un autre problème où nous devions mettre à jour un fichier jar de sécurité sur les machines clientes, via JNLP, il écrase les bibliothèques ${java.home}/lib/security/et la JVM lors de la première exécution.

Cela l'a fait fonctionner.

Mohamed Mansour
la source
2

Voici une version mise à jour de la réponse ntoskrnl . Il contient en outre une fonction pour supprimer le modificateur final comme Arjan mentionné dans les commentaires.

Cette version fonctionne avec JRE 8u111 ou plus récent.

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"));
}
xoné
la source
Cela fonctionne bien, mais la ligne ((Map<?, ?>) perms.get(defaultPolicy)).clear();génère une erreur de compilation. Les commentaires ne semblent pas avoir d'incidence sur sa fonctionnalité. Cette ligne est-elle nécessaire?
Andreas Unterweger
2

Voici une version modifiée du code de @ ntoskrnl avec isRestrictedCryptographyvérification par journalisation slf4j réelleCipher.getMaxAllowedKeyLength et prise en charge de l'initialisation singleton à partir du démarrage de l'application comme ceci:

static {
    UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

Ce code arrêterait correctement de se gâter avec la réflexion lorsque la politique illimitée deviendrait disponible par défaut dans Java 8u162 comme le prédit la réponse 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
la source
-1

Lors de l'installation de votre programme, demandez simplement à l'utilisateur de télécharger un script DOS Batch ou un script shell Bash et copiez le JCE dans l'emplacement système approprié.

J'avais l'habitude de le faire pour un service Web de serveur et au lieu d'un programme d'installation formel, je viens de fournir des scripts pour configurer l'application avant que l'utilisateur ne puisse l'exécuter. Vous pouvez rendre l'application non exécutable jusqu'à ce qu'elle exécute le script de configuration. Vous pouvez également faire en sorte que l'application se plaigne de l'absence de JCE, puis demander à télécharger et à redémarrer l'application?

Djangofan
la source
7
"faire fonctionner mon application sans écraser les fichiers sur la machine de l'utilisateur final"
erickson
J'ai fait une modification complète de ma réponse car ma réponse initiale était fausse.
djangofan