Como evitar a instalação de arquivos de políticas JCE “Unlimited Strength” ao implantar um aplicativo?

Eu tenho um aplicativo que usa criptografia AES de 256 bits, que não é suportado pelo Java fora da checkbox. Eu sei que para que isso funcione corretamente eu instale os jars de força ilimitada JCE na pasta de segurança. Isso é bom para mim, sendo o desenvolvedor, eu posso instalá-los.

Minha pergunta é desde que este 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 a function do aplicativo não seja uma solução atraente.

Existe uma maneira de executar meu aplicativo sem sobrescrever arquivos na máquina do usuário final? Um software de terceiros que pode manipulá-lo sem os arquivos de política instalados? Ou uma maneira de simplesmente referenciar esses arquivos de políticas de dentro de um JAR?

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

  • Instale os arquivos de política de força ilimitada . Embora essa seja provavelmente a solução certa para sua estação de trabalho de desenvolvimento, ela rapidamente se torna uma grande complicação (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 permissions).
  • Ignore a API do JCE e use outra biblioteca de criptografia, como o Bouncy Castle . Essa abordagem requer uma biblioteca extra de 1 MB, o que pode ser um fardo significativo dependendo do aplicativo. Também parece bobagem duplicar a funcionalidade incluída nas bibliotecas padrão. Obviamente, a API também é completamente diferente da interface usual do JCE. (O BC implementa um provedor JCE, mas isso não ajuda, pois as restrições de força da chave são aplicadas antes da entrega na implementação.) Essa solução também não permite usar conjuntos de criptografia TLS (SSL) de 256 bits, porque o As bibliotecas TLS padrão chamam o JCE internamente para determinar quaisquer restrições.

Mas depois há reflection. Existe alguma coisa que você não pode fazer usando a reflection?

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

Basta chamar removeCryptographyRestrictions() de um inicializador estático ou antes de executar qualquer operação criptográfica.

A parte JceSecurity.isRestricted = false é tudo o que é necessário para usar cifras de 256 bits diretamente; no entanto, sem as duas outras operações, o Cipher.getMaxAllowedKeyLength() continuará a Cipher.getMaxAllowedKeyLength() relatórios de 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 pula automaticamente o processo no Java 9 e OpenJDK, onde não é necessário. Sendo um hack feio, afinal, provavelmente não funciona nas VMs de outros fornecedores.

Ele também não funciona no Oracle Java 6, porque as classs JCE privadas são ofuscadas lá. A ofuscação não muda de versão para versão, por isso ainda é tecnicamente possível suportar o Java 6.

Isso agora não é mais necessário para o Java 9 nem para qualquer release recente do Java 6, 7 ou 8. Finalmente! 🙂

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

Versões específicas da edição do JIRA:

  • Java 9: ​​Qualquer lançamento oficial!
  • Java 8u161 ou posterior (disponível agora )
  • Java 7u171 ou posterior (disponível somente através do ‘My Oracle Support’)
  • Java 6u181 ou posterior (disponível somente através do ‘My Oracle Support’)

Observe que, se por algum motivo estranho o comportamento antigo for necessário no Java 9, ele poderá ser definido usando:

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

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) { } } 

O Bouncy Castle ainda requer jars instalados, tanto quanto eu posso dizer.

Eu fiz um pequeno teste e pareceu confirmar isso:

http://www.bouncycastle.org/wiki/display/JA1/Frequently+Asked+Questions

A partir do JDK 8u102, as soluções publicadas que dependem da reflection não funcionarão mais: o campo que essas soluções definem agora é final ( https://bugs.openjdk.java.net/browse/JDK-8149417 ).

Parece que ele está de volta para (a) usando o Bouncy Castle ou (b) instalando os arquivos de políticas do JCE.

Para uma biblioteca de criptografia alternativa, dê uma olhada no Bouncy Castle . Tem AES e muita funcionalidade adicional. É uma biblioteca liberal de código aberto. Você terá que usar a leve e proprietária API do Bouncy Castle para que isso funcione.

Você poderia usar o método

 javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation) 

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

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

Nós tivemos um outro problema em que precisamos atualizar um jar de segurança nas máquinas clientes, através do JNLP, ele sobrescreve as bibliotecas em ${java.home}/lib/security/ e a JVM na primeira execução.

Isso fez funcionar.

Aqui está uma versão atualizada da resposta do ntoskrnl . Além disso, contém uma function para remover o modificador final, como 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")); } 

Durante a instalação do seu programa, basta avisar o usuário e ter um script do Batch do DOS ou um download do script de shell Bash e copiar o JCE no local adequado do sistema.

Eu costumava fazer isso para um servidor web e, em vez de um instalador formal, eu apenas fornecia scripts para configurar o aplicativo antes que o usuário pudesse executá-lo. Você pode tornar o aplicativo inutilizável até que eles executem o script de configuração. Você também pode fazer o aplicativo reclamar que o JCE está faltando e, em seguida, pedir para baixar e reiniciar o aplicativo?

Aqui está uma versão modificada do código do isRestrictedCryptography apresentando a verificação isRestrictedCryptography pelo registro em Cipher.getMaxAllowedKeyLength, Cipher.getMaxAllowedKeyLength real e suporte à boot do singleton a partir do bootstrap do aplicativo desta forma:

 static { UnlimitedKeyStrengthJurisdictionPolicy.ensure(); } 

Este código seria corretamente parar de confundir com a reflection quando a política ilimitada se torna disponível por padrão no Java 8u162 como prevê a resposta do @ 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; // https://stackoverflow.com/questions/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 } }