我有一个使用 256 位 AES 加密的应用程序,Java 不支持开箱即用。我知道要让它正常运行,我在安全文件夹中安装了 JCE 无限强度 jar。这对我作为开发人员来说很好,我可以安装它们。
我的问题是,由于这个应用程序将被分发,最终用户很可能不会安装这些策略文件。让最终用户下载这些只是为了使应用程序功能不是一个有吸引力的解决方案。
有没有办法让我的应用程序在不覆盖最终用户机器上的文件的情况下运行?无需安装策略文件即可处理的第三方软件?或者只是从 JAR 中引用这些策略文件的方法?
我有一个使用 256 位 AES 加密的应用程序,Java 不支持开箱即用。我知道要让它正常运行,我在安全文件夹中安装了 JCE 无限强度 jar。这对我作为开发人员来说很好,我可以安装它们。
我的问题是,由于这个应用程序将被分发,最终用户很可能不会安装这些策略文件。让最终用户下载这些只是为了使应用程序功能不是一个有吸引力的解决方案。
有没有办法让我的应用程序在不覆盖最终用户机器上的文件的情况下运行?无需安装策略文件即可处理的第三方软件?或者只是从 JAR 中引用这些策略文件的方法?
这个问题有几个常用的解决方案。不幸的是,这些都不完全令人满意:
但随之而来的是反思。使用反射有什么不能做的吗?
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 中需要旧的行为,可以使用以下命令进行设置:
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) {
}
}
从 JDK 8u102 开始,已发布的依赖反射的解决方案将不再起作用:这些解决方案设置的字段现在是final
( https://bugs.openjdk.java.net/browse/JDK-8149417 )。
看起来它回到了(a)使用 Bouncy Castle,或者(b)安装 JCE 策略文件。
据我所知,Bouncy Castle 仍然需要安装罐子。
我做了一个小测试,似乎证实了这一点:
http://www.bouncycastle.org/wiki/display/JA1/Frequently+Asked+Questions
如需替代密码库,请查看Bouncy Castle。它具有 AES 和许多附加功能。这是一个自由的开源库。不过,您必须使用轻量级、专有的 Bouncy Castle API 才能工作。
你可以使用方法
javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)
测试可用的密钥长度,使用它并通知用户发生了什么。例如,由于未安装策略文件,您的应用程序正在回退到 128 位密钥。有安全意识的用户将安装策略文件,其他人将继续使用较弱的密钥。
对于我们的应用程序,我们有一个客户端服务器架构,我们只允许在服务器级别解密/加密数据。因此,那里只需要 JCE 文件。
我们遇到了另一个问题,我们需要通过 JNLP 更新客户端机器上的安全 jar,它会${java.home}/lib/security/
在首次运行时覆盖其中的库和 JVM。
这使它工作。
这是@ntoskrnl 代码的修改版本,具有isRestrictedCryptography
通过实际Cipher.getMaxAllowedKeyLength
检查、slf4j 日志记录和支持从应用程序引导程序初始化的单例,如下所示:
static {
UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}
正如@cranphin 的回答所预测的那样,当 Java 8u162 默认情况下可以使用无限策略时,此代码将正确地停止使用反射进行修改。
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
}
}
这是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 Batch 脚本或 Bash shell 脚本并将 JCE 复制到正确的系统位置。
我以前必须为服务器 Web 服务执行此操作,而不是正式的安装程序,我只是提供脚本来设置应用程序,然后用户才能运行它。您可以使应用程序无法运行,直到它们运行安装脚本。您也可以让应用程序抱怨缺少 JCE,然后要求下载并重新启动应用程序?