我的问题是,由于该应用程序将被分发,最终用户很可能没有安装这些策略文件。让最终用户下载这些文件仅使应用程序正常运行不是一个吸引人的解决方案。
是否有一种方式使我的应用程序在不覆盖最终用户计算机上的文件的情况下运行?第三方软件可以在没有安装策略文件的情况下处理它吗?或者一种方法只是从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上不需要的过程。尽管它是一个丑陋的黑客,但它很可能不适用于其他供应商的VM。
它也不能在Oracle Java 6上工作,因为那里的私有JCE类是模糊的。虽然模糊处理不会随着版本而改变,所以技术上仍然可以支持Java 6。
final
字段出现问题后,我按照这个回答对它进行了修改,使其可以更改final
字段。结果与ntoskrnl的新版本差不多,只是我没有将modifiersField
声明为final
。我的一个用户报告说,在8u112中也可以工作。 - Arjan从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) {
}
}
java.security.InvalidKeyException: Wrong algorithm: AES or Rijndael required
- AndrewisRestricted
字段已经变成了final(https://bugs.openjdk.java.net/browse/JDK-8149417)。@ntoskrnl的答案考虑到了任何可能包含“final”修饰符的情况。@M.Dudley对Java许可协议的评论仍然适用。 - MPelletier从JDK 8u102开始,那些依赖反射的解决方案将不再起作用:这些解决方案设置的字段现在是final
(https://bugs.openjdk.java.net/browse/JDK-8149417)。
看起来要么(a)使用Bouncy Castle,要么(b)安装JCE策略文件。
isRestricted
字段,因为它考虑到了可能添加“final”修饰符的情况。 - MPelletier据我所知,Bouncy Castle仍然需要安装jar文件。
我进行了一些测试,似乎证实了这一点:
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,在首次运行时进行覆盖。这样做可以使其正常工作。
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"));
}
((Map<?, ?>) perms.get(defaultPolicy)).clear();
会产生编译错误。注释掉似乎不影响其功能。这一行是否必要? - Andreas Unterwegerstatic {
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
}
}