在部署应用程序时,如何避免安装“无限强度”JCE 策略文件?

我有一个应用程序,使用256位 AES 加密,这是不支持 Java 开箱即用。我知道让这个功能正确,我安装的 JCE 无限强度罐在安全文件夹。这是罚款,我作为开发人员,我可以安装他们。

我的问题是,因为这个应用程序将被分发,最终用户很可能不会有这些政策文件安装。让终端用户下载这些只是为了使应用程序的功能不是一个有吸引力的解决方案。

有没有一种方法可以让我的应用程序运行,而不会覆盖最终用户机器上的文件?一个第三方软件,可以处理它没有安装的政策文件?或者仅从 JAR 中引用这些策略文件的方法?

136600 次浏览

在程序安装期间,只需提示用户下载 DOS 批处理脚本或 Bash shell 脚本,并将 JCE 复制到适当的系统位置。

我曾经不得不为一个服务器 Web 服务这样做,而不是一个正式的安装程序,我只是提供脚本来设置应用程序之前,用户可以运行它。您可以使应用程序不可运行,直到他们运行安装脚本。你也可以让应用程序抱怨 JCE 丢失,然后要求下载并重新启动应用程序?

对于我们的应用程序,我们有一个客户机服务器架构,并且只允许在服务器级别解密/加密数据。因此只需要 JCE 文件。

我们还有另一个问题,需要在客户机上更新安全 jar,通过 JNLP,它在第一次运行时覆盖 ${java.home}/lib/security/和 JVM 中的库。

这样才能成功。

有关替代加密库,请查看 充气城堡。它具有 AES 和许多附加功能。这是一个开放的开源库。不过,您必须使用轻量级的、专有的 Bouncy Castle API 才能实现这个功能。

据我所知,充气城堡还需要安装罐子。

我做了一个小测试,结果似乎证实了这一点:

Http://www.bouncycastle.org/wiki/display/ja1/frequently+asked+questions

对于这个问题,有几种常见的解决办法,但不幸的是,这两种办法都不能令人完全满意:

  • 安装 无限强度政策文件。虽然这对于您的开发工作站来说可能是正确的解决方案,但是让非技术用户在每台计算机上安装文件很快就会成为一个主要的麻烦(如果不是一个障碍的话)。有一个 不可能用于分发程序中的文件; 它们必须安装在 JRE 目录中(由于权限的限制,该目录甚至可能是只读的)。
  • 跳过 JCE API ,使用另一个加密库,如 充气城堡。这种方法需要额外的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()

直接使用256位密码只需要 JceSecurity.isRestricted = false部分; 然而,如果没有其他两个操作,Cipher.getMaxAllowedKeyLength()仍将报告128位,而256位 TLS 密码套件将无法工作。

这段代码可以在 OracleJava7和8上运行,并自动跳过 Java9和 OpenJDK 上不需要的过程。毕竟它是一个丑陋的黑客,可能不适用于其他供应商的虚拟机。

它在 OracleJava6上也无法工作,因为私有 JCE 类在那里被混淆了。不过,这种混淆不会随着版本的变化而改变,所以在技术上仍然可以支持 Java6。

这是解决方案: 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) {
}
}

你可以使用方法

javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

要测试可用的密钥长度,请使用它并通知用户正在进行的操作。例如,声明应用程序由于没有安装策略文件而退回到128位密钥的内容。有安全意识的用户将安装策略文件,其他人将继续使用较弱的密钥。

从 JDK 8u102开始,依赖于反射发布的解决方案将不再有效: 这些解决方案设置的字段现在是 final(https://bugs.openjdk.java.net/browse/JDK-8149417)。

看起来要么(a)使用 Bouncy Castle,要么(b)安装 JCE 策略文件。

现在,对于 爪哇9,以及最近发布的 Java6、7或8,都不再需要这个功能。终于来了!:)

根据 JDK-8170157,现在默认启用无限加密策略。

联合抵抗行动问题的具体版本:

  • Java9(10,11等等) : 任何官方版本!
  • Java8u161或更高版本(可用 现在)
  • Java7u171或更高版本(只可透过「 MyOracle 支援」使用)
  • Java6u181或更高版本(只可透过「 MyOracle 支援」使用)

注意,如果出于某种奇怪的原因需要 Java9中的旧行为,可以使用:

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

这里是 (咒语)答案的更新版本。 它还包含一个函数来删除最后的修饰符,比如注释中提到的 阿里安

此版本适用于 JRE8u111或更新版本。

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

下面是@ntoskrnl 代码的修改版本,它具有 实际的 Cipher.getMaxAllowedKeyLength检查 isRestrictedCryptography、 slf4j 日志记录和从应用程序引导程序支持单例初始化的特性,如下所示:

static {
UnlimitedKeyStrengthJurisdictionPolicy.ensure();
}

正如@ranphin 的回答所预测的那样,当 Java8u162默认情况下可以使用无限策略时,这段代码将正确地停止与反射的混淆。


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