Como evitar a instalação de arquivos de política JCE de "Força Ilimitada" ao implantar um aplicativo?

169

Eu tenho um aplicativo que usa criptografia AES de 256 bits, que não é suportada pelo Java imediatamente. Sei que para que isso funcione corretamente, instalo os frascos de força ilimitados do JCE na pasta de segurança. Isso é bom para mim, como desenvolvedor, posso instalá-los.

Minha pergunta é que, como esse aplicativo será distribuído, os usuários finais provavelmente não terão esses arquivos de política instalados. Fazer o download do usuário final apenas para que o aplicativo funcione não é uma solução atraente.

Existe uma maneira de executar meu aplicativo sem substituir arquivos na máquina do usuário final? Um software de terceiros que pode lidar com isso sem os arquivos de política instalados? Ou uma maneira de apenas referenciar esses arquivos de políticas de dentro de um JAR?

Duncan Jones
fonte
11
Suspeito que a intenção da Sun / Oracle era que o cliente usasse uma cifra menos segura para que a NSA pudesse bisbilhotar a conexão. Não estou brincando ou paranóico, mas a criptografia é tratada como uma arma e há proibições de exportação no compartilhamento de criptografia .
trenó

Respostas:

175

Existem algumas soluções comumente citadas para esse problema. Infelizmente, nenhum deles é totalmente satisfatório:

  • Instale os arquivos de política de força ilimitados . Embora essa seja provavelmente a solução certa para sua estação de trabalho de desenvolvimento, ela rapidamente se torna um grande aborrecimento (se não um obstáculo) para que usuários não técnicos instalem os arquivos em todos os computadores. Não há como distribuir os arquivos com o seu programa; eles devem ser instalados no diretório JRE (que pode até ser somente leitura devido a permissões).
  • Ignore a API do JCE e use outra biblioteca de criptografia como o Bouncy Castle . Essa abordagem requer uma biblioteca extra de 1 MB, que pode ser uma carga significativa, dependendo do aplicativo. Também é bobagem duplicar a funcionalidade incluída nas bibliotecas padrão. Obviamente, a API também é completamente diferente da interface JCE usual. (O BC implementa um provedor JCE, mas isso não ajuda, porque as restrições de força de chave são aplicadas antes da entrega à implementação.) Essa solução também não permitirá que você use conjuntos de cifras TLS (SSL) de 256 bits, porque o bibliotecas TLS padrão chamam o JCE internamente para determinar quaisquer restrições.

Mas depois há reflexão. Existe algo que você não possa fazer usando reflexão?

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

Simplesmente chame removeCryptographyRestrictions()de um inicializador estático ou algo semelhante antes de executar qualquer operação criptográfica.

A JceSecurity.isRestricted = falseparte é tudo o que é necessário para usar cifras de 256 bits diretamente; no entanto, sem as duas outras operações, Cipher.getMaxAllowedKeyLength()o relatório continuará sendo reportado por 128, e os conjuntos de criptografia TLS de 256 bits não funcionarão.

Esse código funciona no Oracle Java 7 e 8 e ignora automaticamente o processo no Java 9 e no OpenJDK onde não é necessário. Sendo um truque feio, afinal, provavelmente não funciona nas VMs de outros fornecedores.

Também não funciona no Oracle Java 6, porque as classes JCE privadas são ofuscadas por lá. A ofuscação não muda de versão para versão, portanto, ainda é tecnicamente possível oferecer suporte ao Java 6.

ntoskrnl
fonte
23
A solução de reflexão pode violar o Contrato de licença Java : "F. RESTRIÇÕES DE TECNOLOGIA JAVA. Você não pode ... alterar o comportamento de ... classes, interfaces ou subpacotes que são de alguma forma identificados como 'java', 'javax' , 'sol', 'oráculo' ou convenção similar ... "
M. Dudley
14
@ M.Dudley poderia ser. Consulte um advogado antes de enviar um produto que contenha esse código, se for do seu interesse.
Ntskrnl
3
@peabody A inclusão de um JRE de 100 MB no seu programa é certamente uma opção em alguns casos. Caso contrário, os usuários ainda terão que instalar os arquivos de políticas manualmente, mesmo que você os inclua no programa (por vários motivos, como permissões de arquivo). Na minha experiência, muitos usuários não são capazes disso.
Ntskrnl
8
Parece que a solução de reflexão parou de funcionar no 1.8.0_112. Ele funciona em 1.8.0_111, mas não 112.
John L
3
@JohnL Eu uso isso em um aplicativo. Depois de ter problemas com o finalcampo em 8u111, modifiquei-o para que ele possa alterar o campo final, seguindo esta resposta . O resultado é quase o mesmo que a nova versão do ntoskrnl, exceto que eu não declarei modifiersFieldcomo final. Um dos meus usuários relata que ele também funciona no 8u112.
Arjan #
87

Agora, isso não é mais necessário para o Java 9 , nem para qualquer versão recente do Java 6, 7 ou 8. Finalmente! :)

De acordo com JDK-8170157 , a política criptográfica ilimitada agora está ativada por padrão.

Versões específicas do problema do JIRA:

  • Java 9 (10, 11, etc.): Qualquer versão oficial!
  • Java 8u161 ou posterior (disponível agora )
  • Java 7u171 ou posterior (disponível apenas no 'Suporte do My Oracle')
  • Java 6u181 ou posterior (disponível apenas no 'Suporte do My Oracle')

Observe que, por algum motivo estranho, o comportamento antigo é necessário no Java 9, ele pode ser configurado usando:

Security.setProperty("crypto.policy", "limited");
cranphin
fonte
4
De fato, essa política é o padrão, portanto, nenhuma ação é necessária no Java 9!
Ntskrnl
A partir de 14/01/2018 (o Oracle JDK mais recente é 8u151 / 152), isso ainda não está ativado por padrão no Java 8, bem mais de um ano após a resposta original ter sido escrita ... No entanto, de acordo com java.com/en/jre -jdk-cryptoroadmap.html isto é destinado ao GA em 16/01/2018
Alex
No meu caso, e para eu obter uma Marca de A neste site: ssllabs.com/ssltest ... Tenho que configurá-la desta maneira: Security.setProperty ("crypto.policy", "ilimitado"); Então ... Configurar server.ssl.ciphers em meus applications.properties com 256 baseados em algoritmos indicado neste artigo -> weakdh.org/sysadmin.html
Artanis Zeratul
Também relevante para o OpenJDK 8-Installations. Consulte: stackoverlow-Article: A política do JCE vem com o openjdk 8?
leole 27/03
22

Aqui está a solução: 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
fonte
Esta é a mesma solução que a minha, exceto sem a parte "defaultPolicy". A postagem do blog é datada após a minha resposta.
Ntskrnl 12/04
1
Mas isso é certo? Em tempo real, esse código pode desafiar a segurança do aplicativo? Não tenho certeza, por favor me ajude a entender seu impacto.
Dish
1
Eu recebo este erro depois de executar o seguinte: #: #java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
Andy Andy
3
A partir do Java 8 build 111, essa solução será insuficiente, pois o isRestrictedcampo se tornou final ( bugs.openjdk.java.net/browse/JDK-8149417 ). A resposta do @ ntoskrnl cuida de qualquer possível inclusão de um modificador "final". O comentário de @M.Dudley sobre o Contrato de Licença Java também se aplica.
MPelletier 20/10/19
13

A partir do JDK 8u102, as soluções postadas baseadas na reflexão não funcionarão mais: o campo que essas soluções configuradas estão agora final( https://bugs.openjdk.java.net/browse/JDK-8149417 ).

Parece que voltou a (a) usar o Bouncy Castle ou (b) instalar os arquivos de políticas do JCE.

Sam Roberton
fonte
7
Você sempre pode usar mais reflexões stackoverflow.com/questions/3301635/…
Universal Electricity
Sim, a solução da @ M.Dudley ainda funcionará no isRestrictedcampo, porque cuida de uma possível adição de um modificador "final".
MPelletier
1
A nova versão do JDK 8u151 possui "Nova propriedade de segurança para controlar a política de criptografia". Conclusão: remova o "#" da linha "# crypto.policy = unlimited" em "lib \ security \ java.security": oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html
hemisphire
8

Para uma biblioteca de criptografia alternativa, dê uma olhada no Bouncy Castle . Possui AES e muitas funcionalidades adicionais. É uma biblioteca liberal de código aberto. Você precisará usar a API leve e proprietária do Bouncy Castle para que isso funcione.

Maarten Bodewes
fonte
19
Eles são um ótimo provedor de criptografia, mas ainda exigem o arquivo JCE de força ilimitada para trabalhar com chaves grandes.
John Meagher
16
Se você usar a API do Bouncy Castle diretamente, não precisará dos arquivos de força ilimitados.
Laz
4

Você poderia usar o método

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

para testar o comprimento da chave disponível, use isso e informe o usuário sobre o que está acontecendo. Algo que indica que seu aplicativo está retornando às chaves de 128 bits devido aos arquivos de política não estarem instalados, por exemplo. Usuários preocupados com segurança instalarão os arquivos de políticas, outros continuarão usando chaves mais fracas.

Christian Schulte
fonte
3

Para nosso aplicativo, tínhamos uma arquitetura de servidor cliente e permitíamos apenas descriptografar / criptografar dados no nível do servidor. Portanto, os arquivos JCE são necessários apenas lá.

Tivemos outro problema em que precisávamos atualizar um jar de segurança nas máquinas clientes, por meio do JNLP, ele substitui as bibliotecas ${java.home}/lib/security/e a JVM na primeira execução.

Isso fez funcionar.

Mohamed Mansour
fonte
2

Aqui está uma versão atualizada da resposta ntoskrnl . Além disso, contém uma função para remover o modificador final como o Arjan mencionado nos comentários.

Esta versão funciona com o JRE 8u111 ou mais 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"));
}
xonado
fonte
Funciona bem, mas a linha ((Map<?, ?>) perms.get(defaultPolicy)).clear();gera um erro do compilador. Comentar não parece afetar sua funcionalidade. Essa linha é necessária?
Andreas Unterweger
2

Aqui está uma versão modificada do código do @ ntoskrnl, apresentando isRestrictedCryptographycheck por log slf4j realCipher.getMaxAllowedKeyLength e suporte à inicialização de singleton a partir do bootstrap do aplicativo como este:

static {
    UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

Esse código para de corrigir corretamente quando a política ilimitada se torna disponível por padrão no Java 8u162, como a resposta do @ cranphin prevê.


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
fonte
-1

Durante a instalação do seu programa, basta solicitar ao usuário e fazer o download de um script de lote do DOS ou de shell do Bash e copiar o JCE no local apropriado do sistema.

Eu costumava fazer isso para um serviço da web do servidor e, em vez de um instalador formal, fornecia apenas scripts para configurar o aplicativo antes que o usuário pudesse executá-lo. Você pode tornar o aplicativo impossível de executar até que eles executem o script de instalação. Você também pode fazer com que o aplicativo reclame que o JCE está ausente e peça para baixar e reiniciar o aplicativo?

djangofan
fonte
7
"fazer o meu aplicativo executado sem sobrescrever arquivos na máquina do usuário final"
Erickson
Fiz uma edição completa da minha resposta, pois minha resposta inicial era errada.
djangofan