Come evitare l'installazione di file di criteri JCE "Unlimited Strength" durante la distribuzione di un'applicazione?


169

Ho un'app che utilizza la crittografia AES a 256 bit che non è supportata da Java. So che questo funziona correttamente, installo i vasetti JCE di forza illimitata nella cartella di sicurezza. Questo va bene per me, essendo lo sviluppatore, posso installarli.

La mia domanda è che poiché questa app verrà distribuita, molto probabilmente agli utenti finali non saranno installati questi file delle politiche. Avere l'utente finale a scaricarli solo per far funzionare l'app non è una soluzione interessante.

Esiste un modo per far funzionare la mia app senza sovrascrivere i file sul computer dell'utente finale? Un software di terze parti in grado di gestirlo senza i file dei criteri installati? O un modo per fare semplicemente riferimento a questi file delle politiche all'interno di un JAR?




11
Sospetto che l'intenzione di Sun / Oracle fosse che il client avrebbe usato un codice meno sicuro in modo che l'NSA potesse curiosare sulla connessione. Non sto scherzando o essendo paranoico, ma la crittografia è trattata come un'arma e ci sono divieti di esportazione sulla condivisione della crittografia .
Slitta,

Risposte:


175

Esistono un paio di soluzioni comunemente citate a questo problema. Sfortunatamente nessuno di questi è del tutto soddisfacente:

  • Installa i file dei criteri di resistenza illimitati . Sebbene questa sia probabilmente la soluzione giusta per la tua workstation di sviluppo, diventa rapidamente una seccatura maggiore (se non un blocco stradale) avere utenti non tecnici che installano i file su ogni computer. Non è possibile distribuire i file con il programma; devono essere installati nella directory JRE (che potrebbe anche essere di sola lettura a causa delle autorizzazioni).
  • Salta l'API JCE e utilizza un'altra libreria di crittografia come Bouncy Castle . Questo approccio richiede una libreria aggiuntiva da 1 MB, che può essere un onere significativo a seconda dell'applicazione. È anche stupido duplicare le funzionalità incluse nelle librerie standard. Ovviamente, anche l'API è completamente diversa dalla solita interfaccia JCE. (BC implementa un provider JCE, ma ciò non aiuta perché le restrizioni sulla forza chiave vengono applicate prima di passare all'implementazione.) Questa soluzione non consente inoltre di utilizzare suite di crittografia TLS (SSL) a 256 bit, poiché le librerie TLS standard chiamano JCE internamente per determinare eventuali restrizioni.

Ma poi c'è la riflessione. C'è qualcosa che non puoi fare usando la riflessione?

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

È sufficiente chiamare removeCryptographyRestrictions()da un inizializzatore statico o simile prima di eseguire qualsiasi operazione crittografica.

La JceSecurity.isRestricted = falseparte è tutto ciò che serve per usare direttamente le cifre a 256 bit; tuttavia, senza le altre due operazioni, Cipher.getMaxAllowedKeyLength()continuerà a generare report a 128 e le suite di crittografia TLS a 256 bit non funzioneranno.

Questo codice funziona su Oracle Java 7 e 8 e salta automaticamente il processo su Java 9 e OpenJDK dove non è necessario. Dopotutto, essendo un brutto hack, probabilmente non funziona con le macchine virtuali di altri fornitori.

Inoltre non funziona su Oracle Java 6, perché le classi JCE private sono offuscate lì. Tuttavia, l'offuscamento non cambia da versione a versione, quindi è ancora tecnicamente possibile supportare Java 6.


23
La soluzione di riflessione potrebbe violare il Contratto di licenza Java : "F. LIMITAZIONI DELLA TECNOLOGIA JAVA. Non è possibile ... modificare il comportamento di ... classi, interfacce o pacchetti secondari che sono in qualche modo identificati come" java "," javax " , "sole", "oracolo" o convenzione simile ... "
M. Dudley,

14
@ M.Dudley Potrebbe essere. Verificare con un avvocato prima di spedire un prodotto che contiene questo pezzo di codice se lo riguarda.
ntoskrnl,

3
@peabody Includere un JRE da 100 MB con il tuo programma è certamente un'opzione in alcuni casi. In caso contrario, gli utenti dovranno comunque installare manualmente i file delle politiche, anche se li includi nel tuo programma (a causa di vari motivi come le autorizzazioni dei file). Nella mia esperienza, molti utenti non sono in grado di farlo.
ntoskrnl,

8
Sembra che la soluzione di riflessione abbia appena smesso di funzionare in 1.8.0_112. Funziona in 1.8.0_111, ma non in 112.
John L

3
@JohnL Lo uso in un'applicazione. Dopo aver incontrato dei problemi con il finalcampo in 8u111, l'ho modificato in modo che possa cambiare il campo finale, seguendo questa risposta . Il risultato è quasi lo stesso della nuova versione di ntoskrnl, tranne per il fatto che non ho dichiarato modifiersFieldcome final. Uno dei miei utenti riferisce che funziona anche in 8u112.
Arjan,

87

Questo non è più necessario per Java 9 , né per alcuna versione recente di Java 6, 7 o 8. Finalmente! :)

Secondo JDK-8170157 , la politica crittografica illimitata è ora abilitata per impostazione predefinita.

Versioni specifiche dal problema JIRA:

  • Java 9 (10, 11, ecc.): Qualsiasi versione ufficiale!
  • Java 8u161 o successivo (disponibile ora )
  • Java 7u171 o successivo (disponibile solo tramite "My Oracle Support")
  • Java 6u181 o successivo (disponibile solo tramite "My Oracle Support")

Nota che se per qualche strana ragione è necessario il vecchio comportamento in Java 9, può essere impostato usando:

Security.setProperty("crypto.policy", "limited");

4
In realtà, questa politica è l'impostazione predefinita, quindi non sono necessarie azioni in Java 9!
ntoskrnl,

A partire dal 2018/01/14 (l'ultimo Oracle JDK è 8u151 / 152), questo non è ancora abilitato per impostazione predefinita su Java 8, ben oltre un anno dopo che questa risposta è stata scritta originariamente ... Tuttavia, secondo java.com/en/jre -jdk-cryptoroadmap.html questo è destinato a GA il 16/01/2018
Alex

Nel mio caso, e per me ottenere un Mark of A in questo sito: ssllabs.com/ssltest ... Devo impostarlo in questo modo: Security.setProperty ("crypto.policy", "unlimited"); quindi ... imposta server.ssl.ciphers nelle mie applicazioni.properties con algoritmi basati su 256 indicati in questo articolo -> weakdh.org/sysadmin.html
Artanis Zeratul

Rilevante anche per OpenJDK 8-Installations. Vedi: stackoverlow-Article: La politica JCE è associata a openjdk 8?
leole,

22

Ecco la soluzione: 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) {
    }
}

Questa è la stessa soluzione della mia, tranne senza la parte "defaultPolicy". Il post sul blog è datato dopo la mia risposta.
ntoskrnl,

1
Ma è questa la cosa giusta da fare? In tempo reale questo codice può mettere in discussione la sicurezza dell'applicazione? Non sono sicuro per favore aiutami a capire che impatto ha.
Piatto

1
Ottengo questo errore dopo aver eseguito questo:java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
Andy

3
A partire da Java 8 build 111, questa soluzione non sarà sufficiente, poiché il isRestrictedcampo è diventato definitivo ( bugs.openjdk.java.net/browse/JDK-8149417 ). La risposta di @ ntoskrnl si occupa di ogni possibile inclusione di un modificatore "finale". Si applica ancora anche il commento di M.Dudley sul Contratto di licenza Java.
MPelletier,


13

A partire da JDK 8u102, le soluzioni pubblicate basate sulla riflessione non funzioneranno più: il campo impostato da queste soluzioni è ora final( https://bugs.openjdk.java.net/browse/JDK-8149417 ).

Sembra che sia tornato a (a) utilizzando Bouncy Castle o (b) all'installazione dei file dei criteri JCE.



Sì, la soluzione di @ M.Dudley continuerà a funzionare per il isRestrictedcampo, poiché si occupa di una possibile aggiunta di un modificatore "finale".
MPelletier,

1
Nuova versione JDK 8u151 ha "Nuova proprietà di sicurezza per controllare la politica di crittografia". Bottom line: rimuovi il "#" dalla riga "# crypto.policy = unlimited" in "lib \ security \ java.security": oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html
hemisphire

8

Per una libreria di crittografia alternativa, dai un'occhiata a Bouncy Castle . Ha AES e molte funzionalità aggiuntive. È una biblioteca open source liberale. Tuttavia, dovrai utilizzare l'API Bouncy Castle leggera e proprietaria per far funzionare tutto questo.


19
Sono un ottimo fornitore di crittografia, ma richiedono comunque il file JCE a forza illimitata per funzionare con chiavi di grandi dimensioni.
John Meagher,

16
Se usi direttamente l'API Bouncy Castle non hai bisogno dei file di forza illimitati.
Laz

4

Puoi usare il metodo

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

per testare la lunghezza della chiave disponibile, usala e informa l'utente su cosa sta succedendo. Qualcosa che afferma che l'applicazione sta tornando alle chiavi a 128 bit a causa dei file dei criteri non installati, ad esempio. Gli utenti attenti alla sicurezza installeranno i file delle politiche, altri continueranno a utilizzare chiavi più deboli.


3

Per la nostra applicazione, avevamo un'architettura server client e consentivamo solo la decrittografia / crittografia dei dati a livello di server. Quindi i file JCE sono necessari solo lì.

Abbiamo avuto un altro problema in cui dovevamo aggiornare un vaso di sicurezza sui computer client, tramite JNLP, sovrascrivendo le librerie ${java.home}/lib/security/e JVM al primo avvio .

Questo l'ha fatto funzionare.


2

Ecco una versione aggiornata della risposta ntoskrnl . Contiene inoltre una funzione per rimuovere il modificatore finale come Arjan menzionato nei commenti.

Questa versione funziona con JRE 8u111 o più recente.

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

Funziona bene, ma la riga ((Map<?, ?>) perms.get(defaultPolicy)).clear();produce un errore del compilatore. Commentare non sembra influire sulla sua funzionalità. Questa linea è necessaria?
Andreas Unterweger,

2

Ecco una versione modificata del codice isRestrictedCryptographydi @ntoskrnl con controllo tramite registrazione effettivaCipher.getMaxAllowedKeyLength slf4j e supporto dell'inizializzazione singleton dall'applicazione bootstrap in questo modo:

static {
    UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

Questo codice smetterebbe correttamente di alterarsi con la riflessione quando per impostazione predefinita in Java 8u162 saranno disponibili criteri illimitati come prevede la risposta di @ 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
    }
}

-1

Durante l'installazione del programma, basta richiedere all'utente e scaricare uno script DOS Batch o uno script shell Bash e copiare il JCE nella posizione di sistema corretta.

Prima dovevo farlo per un server web e invece di un programma di installazione formale, fornivo solo degli script per configurare l'app prima che l'utente potesse eseguirla. È possibile rendere l'app non eseguibile fino a quando non eseguono lo script di installazione. Potresti anche fare in modo che l'app si lamenta della mancanza del JCE e quindi chiedere di scaricare e riavviare l'app?


7
"fai funzionare la mia app senza sovrascrivere i file sul computer dell'utente finale"
erickson

Ho fatto una modifica completa della mia risposta poiché la mia risposta iniziale era sbagliata.
Djangofan,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.