如何避免在部署应用程序时安装“无限强度”JCE策略文件?
我有一个使用256位AES加密的应用程序,Java不支持开箱即用。 我知道得到这个功能正常我安装JCE无限强度罐在安全文件夹。 这对我是开发者来说很好,我可以安装它们。
我的问题是,因为这个应用程序将被分发,最终用户很可能不会有这些策略文件安装。 让最终用户下载这些只是为了使应用程序功能不是一个有吸引力的解决方案。
有没有办法让我的应用程序运行而不覆盖最终用户机器上的文件? 可以在没有安装策略文件的情况下处理它的第三方软件? 或者从JAR中引用这些策略文件的方法?
这个问题有几个通常引用的解决方案。 不幸的是,这些都不完全令人满意:
- 安装无限强度策略文件 。 虽然这可能是您的开发工作站的正确解决方案,但如果非技术用户在每台计算机上安装文件,它很快会成为一个主要麻烦(如果不是障碍)。 没有办法与您的程序分发文件; 它们必须安装在JRE目录下(由于权限,它甚至可能是只读的)。
- 跳过JCE API并使用另一个密码学库,如Bouncy Castle 。 这种方法需要额外的1MB库,这可能是一个重要的负担,取决于应用程序。 复制标准库中包含的功能也感觉很愚蠢。 显然,这个API也与通常的JCE接口完全不同。 (BC确实实现了一个JCE提供程序,但是这并没有帮助,因为在移交到实现之前应用了关键强度限制。)此解决方案也不允许使用256位TLS(SSL)密码套件,因为标准TLS库在内部调用JCE来确定任何限制。
但是,这是反思。 有什么你不能使用反射吗?
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")); }
在执行任何加密操作之前,只需从静态初始化程序中调用removeCryptographyRestrictions()
。
JceSecurity.isRestricted = false
部分是直接使用256位密码所需的全部内容; 但是,如果没有其他两个操作,则Cipher.getMaxAllowedKeyLength()
仍将保持报告128,而256位TLS密码套件将不起作用。
此代码适用于Oracle Java 7和8,并自动跳过不需要Java 9和OpenJDK的进程。 毕竟是一个丑陋的黑客,它可能不适用于其他厂商的虚拟机。
它也不能在Oracle Java 6上运行,因为私有的JCE类在那里被混淆了。 虽然混淆并不是从版本到版本的变化,所以在技术上仍然可以支持Java 6。
现在不再需要Java 9 ,也不需要Java 6,7或8的最新版本。最后! 🙂
根据JDK-8170157 ,默认情况下,无限加密策略现在处于启用状态。
JIRA问题的特定版本:
- Java 9:任何官方发布!
- Java 8u162或更高版本
- Java 7u181或更高版本
- Java 6u191或更高版本
请注意,如果出于某种奇怪的原因,Java 9中需要使用旧的行为,则可以使用以下方法进行设置:
Security.setProperty("crypto.policy", "limited");
这里是解决方案: 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) { } }
充气城堡仍然需要安装罐子,据我所知。
我做了一个小测试,似乎证实了这一点:
http://www.bouncycastle.org/wiki/display/JA1/Frequently+Asked+Questions
从JDK 8u102开始,依赖反射的发布解决方案将不再起作用:这些解决方案设置的字段现在是final
( https://bugs.openjdk.java.net/browse/JDK-8149417 )。
看起来可以回到(a)使用Bouncy Castle,或者(b)安装JCE策略文件。
对于另一个密码学图书馆,看一看Bouncy Castle 。 它有AES和很多附加功能。 这是一个自由的开源库。 您将不得不使用轻量级,专有的Bouncy Castle API来实现这个功能。
你可以使用方法
javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)
测试可用的密钥长度,使用它并通知用户正在发生的事情。 例如,由于未安装策略文件,某些事情表明您的应用程序正在回退到128位密钥。 有安全意识的用户将安装策略文件,其他人将继续使用较弱的密钥。
对于我们的应用程序,我们有一个客户端服务器架构,我们只允许在服务器级别解密/加密数据。 因此只有那里需要JCE文件。
我们还遇到了另一个问题:我们需要通过JNLP更新客户端机器上的安全jar,并在第一次运行时覆盖${java.home}/lib/security/
和JVM。
这使它的工作。
这是ntoskrnl答案的更新版本。 它还包含一个函数,用于删除评论中提到的Arjan之类的最终修饰符。
此版本适用于JRE 8u111或更新版本。
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")); }
在安装程序的过程中,只需提示用户,然后下载DOS批处理脚本或Bash shell脚本,并将JCE复制到适当的系统位置。
我曾经不得不为服务器web服务做这件事,而不是正式的安装程序,我只是提供脚本来安装应用程序之前,用户可以运行它。 您可以使应用程序无法运行,直到他们运行安装脚本。 你也可以让应用程序抱怨JCE丢失,然后要求下载并重新启动应用程序?
这里是@ ntoskrnl代码的修改版本,其特征isRestrictedCryptography
通过实际的Cipher.getMaxAllowedKeyLength
,slf4j日志记录isRestrictedCryptography
检查isRestrictedCryptography
检查,并支持从应用程序引导启动单例初始化,如下所示:
static { UnlimitedKeyStrengthJurisdictionPolicy.ensure(); }
这个代码将正确地停止反射,当在Java 8u162默认无限制的策略成为可用时,@ 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 } }