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

174
我有一个应用程序使用256位AES加密,这不是Java的默认支持。我知道要使其正确运行,我需要在安全文件夹中安装JCE无限强度的jar文件。作为开发人员,这对我来说很好,我可以安装它们。
我的问题是,由于该应用程序将被分发,最终用户很可能没有安装这些策略文件。让最终用户下载这些文件仅使应用程序正常运行不是一个吸引人的解决方案。
是否有一种方式使我的应用程序在不覆盖最终用户计算机上的文件的情况下运行?第三方软件可以在没有安装策略文件的情况下处理它吗?或者一种方法只是从JAR中引用这些策略文件?

1
请查看这里:http://docs.oracle.com/javase/1.5.0/docs/guide/security/jce/JCERefGuide.html#ExemptApps - Petey B
13
我怀疑 Sun/Oracle 的意图是让客户使用不太安全的密码,以便 NSA 可以窃听连接。我并不是开玩笑或过度担心,但密码学被视为武器,分享加密技术存在出口限制。 - Sled
11个回答

176

这个问题有几种常见的解决方案,但不幸的是,这些方案都不完全令人满意:

  • 安装无限制强度策略文件虽然这可能是您的开发工作站的正确解决方案,但让非技术用户在每台计算机上安装文件很快就会成为一个主要麻烦(如果不是障碍)。无法通过程序分发文件;它们必须安装在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上不需要的过程。尽管它是一个丑陋的黑客,但它很可能不适用于其他供应商的VM。

它也不能在Oracle Java 6上工作,因为那里的私有JCE类是模糊的。虽然模糊处理不会随着版本而改变,所以技术上仍然可以支持Java 6。


25
反射解决方案可能会违反Java许可协议的规定:“F. JAVA TECHNOLOGY RESTRICTIONS. 您不得……更改任何以'java'、'javax'、'sun'、'oracle'或类似约定方式标识的类、接口或子包的行为……" - sourcenouveau
15
如果你关心这个问题,建议在发货产品中包含此代码前咨询一位律师。 - ntoskrnl
4
在某些情况下,将100MB的JRE与您的程序一起提供是可行的选择。但如果不这样做,即使您将策略文件与您的程序一起提供(由于各种原因,如文件权限),用户仍然需要手动安装这些文件。根据我的经验,许多用户无法做到这一点。 - ntoskrnl
8
似乎在1.8.0_112版本中反射解决方案停止工作了。它在1.8.0_111版本中有效,但在112版本中无效。 - John L
3
@JohnL 我在一个应用程序中使用这个。在8u111的final字段出现问题后,我按照这个回答对它进行了修改,使其可以更改final字段。结果与ntoskrnl的新版本差不多,只是我没有将modifiersField声明为final。我的一个用户报告说,在8u112中也可以工作。 - Arjan
显示剩余4条评论

90

从Java 9开始,以及任何近期的Java 6、7或8版本中,这不再需要。终于! :)

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

JIRA问题的具体版本:

  • Java 9(10、11等):任何官方发布版本!
  • Java 8u161或更高版本(现在可用)
  • Java 7u171或更高版本(仅透过 'My Oracle Support' 可获得)
  • Java 6u181或更高版本(仅透过 'My Oracle Support' 可获得)

请注意,如果在Java 9中出于某种奇怪的原因需要旧有行为,则可以使用以下设置:

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

4
实际上,这项政策是默认设置,在Java 9中不需要采取任何措施! - ntoskrnl
截至2018年1月14日(最新的Oracle JDK是8u151/152),即使在此答案最初编写一年多之后,Java 8仍未默认启用此功能...但是根据https://www.java.com/en/jre-jdk-cryptoroadmap.html,这项功能预计将于2018年1月16日正式发布。 - Alex
在我的情况下,为了在此网站 https://www.ssllabs.com/ssltest/ 获得 A 级评分,我必须这样设置:Security.setProperty("crypto.policy", "unlimited"); 然后...在我的 applications.properties 中设置 server.ssl.ciphers,并使用本文中指定的基于 256 的算法 --> https://weakdh.org/sysadmin.html - Artanis Zeratul
同样适用于OpenJDK 8安装。请参见:stackoverlow文章:JCE策略是否与openjdk 8捆绑? - leole
希望在Java 11中也不需要这个,对吧? - rinilnath

22

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

这个解决方案和我的一样,只是没有“defaultPolicy”部分。博客文章发布日期在我的回答之后。 - ntoskrnl
1
但是这样做对吗?在实时环境中,这段代码是否会挑战应用程序的安全性?我不确定,请帮我理解它的影响。 - coretechie
1
运行后我得到了这个错误:java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required - Andrew
4
从Java 8 build 111版本开始,这个解决方案已经不够用了,因为isRestricted字段已经变成了final(https://bugs.openjdk.java.net/browse/JDK-8149417)。@ntoskrnl的答案考虑到了任何可能包含“final”修饰符的情况。@M.Dudley对Java许可协议的评论仍然适用。 - MPelletier

14

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

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


7
你总是可以使用更多的反思。使用Java反射,可以更改私有静态常量字段。 - Universal Electricity
是的,@M.Dudley的解决方案仍然适用于isRestricted字段,因为它考虑到了可能添加“final”修饰符的情况。 - MPelletier
1
新发布的JDK 8u151具有“新的安全属性来控制加密策略”。底线:从“lib \ security \ java.security”中删除行“# crypto.policy = unlimited”的“#”符号:http://www.oracle.com/technetwork/java/javase/8u151-relnotes-3850493.html - hemisphire

13

10

如果你需要一种替代的加密库,可以看看Bouncy Castle。它包含AES和许多其他功能。这是一个自由开源的库。但是,你需要使用轻量级专有的Bouncy Castle API才能使其正常工作。


20
他们是一家很棒的加密货币提供商,但仍需要无限强度的JCE文件才能处理大型密钥。 - John Meagher
18
如果直接使用Bouncy Castle API,您就不需要无限强度文件。 - laz

5
你可以使用方法。
javax.crypto.Cipher.getMaxAllowedKeyLength(String transformation)

要测试可用密钥长度,请使用该密钥并告知用户正在进行的操作。例如,说明您的应用程序由于未安装策略文件而退回到使用128位密钥。安全意识较高的用户将安装策略文件,而其他用户则将继续使用较弱的密钥。


3

针对我们的应用程序,我们采用了客户端服务器架构,并只允许在服务器层解密/加密数据。因此,JCE文件仅在服务器端需要。

我们还遇到另一个问题,需要通过JNLP在客户机器上更新安全jar文件,它会覆盖${java.home}/lib/security/中的库和JVM,在首次运行时进行覆盖。这样做可以使其正常工作。


3
这里是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"));
}

1
它的功能很好,但是这一行 ((Map<?, ?>) perms.get(defaultPolicy)).clear(); 会产生编译错误。注释掉似乎不影响其功能。这一行是否必要? - Andreas Unterweger

2
这是@ntoskrnl代码的修改版,增加了通过实际的Cipher.getMaxAllowedKeyLength检查isRestrictedCryptography、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://dev59.com/anM_5IYBdhLWcg3w43lt
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
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接