转:https://my.oschina.net/dwbin/blog/1560379
前言:
遇到下面的问题,结果百度的时候千篇一律的一种解决方案,这里把自己research出来的内容一并留存下来,希望对后来人有用。
这个问题的stacktrace:
java.security.InvalidKeyException: Illegal key size or default parameters
at javax.crypto.Cipher.checkCryptoPerm(Cipher.java:1011)
at javax.crypto.Cipher.implInit(Cipher.java:786)
at javax.crypto.Cipher.chooseProvider(Cipher.java:849)
at javax.crypto.Cipher.init(Cipher.java:1213)
at javax.crypto.Cipher.init(Cipher.java:1153)
百度出来的东西都是一致的,美国限制技术出口,禁止128以上的AES秘钥,所以oralce搞了两个policy文件来限制。要做这个事情,就需要到oracle下载两个新的policy文件。乍看挺好,实际上比较扯,为了使用一个算法居然要增加一个不可控的运维工作。
然后实际上这个东西还是有其他解决方案的,而且不止一个,关键在于要肯动脑子:
使用其他资源包
限制是JDK给的,那么OK加解密使用的包我们换一下,不用JDK的内容,改成apache-commons-crypto
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-crypto</artifactId>
<version>1.0.0</version>
</dependency>
但是这个货有个限制,在linux上面没啥,在windows上面需要你安装openssl,如果实在想要使用的请自行安装SSL(略复杂,由于不想增加开发人员的难度,所以没有采用这个方法)。
相应的接口代码:
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.crypto.examples;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Properties;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.cipher.CryptoCipherFactory;
import org.apache.commons.crypto.cipher.CryptoCipherFactory.CipherProvider;
import org.apache.commons.crypto.utils.Utils;
/**
* Example showing use of the CryptoCipher API using a byte array
*/
public class CipherByteArrayExample {
public static void main(String[] args) throws Exception {
final SecretKeySpec key = new SecretKeySpec(getUTF8Bytes("1234567890123456"),"AES");
final IvParameterSpec iv = new IvParameterSpec(getUTF8Bytes("1234567890123456"));
Properties properties = new Properties();
properties.setProperty(CryptoCipherFactory.CLASSES_KEY, CipherProvider.OPENSSL.getClassName());
//Creates a CryptoCipher instance with the transformation and properties.
final String transform = "AES/CBC/PKCS5Padding";
CryptoCipher encipher = Utils.getCipherInstance(transform, properties);
System.out.println("Cipher: " + encipher.getClass().getCanonicalName());
final String sampleInput = "hello world!";
System.out.println("input: " + sampleInput);
byte[] input = getUTF8Bytes(sampleInput);
byte[] output = new byte[32];
//Initializes the cipher with ENCRYPT_MODE, key and iv.
encipher.init(Cipher.ENCRYPT_MODE, key, iv);
//Continues a multiple-part encryption/decryption operation for byte array.
int updateBytes = encipher.update(input, 0, input.length, output, 0);
System.out.println(updateBytes);
//We must call doFinal at the end of encryption/decryption.
int finalBytes = encipher.doFinal(input, 0, 0, output, updateBytes);
System.out.println(finalBytes);
//Closes the cipher.
encipher.close();
System.out.println(Arrays.toString(Arrays.copyOf(output, updateBytes+finalBytes)));
// Now reverse the process using a different implementation with the same settings
properties.setProperty(CryptoCipherFactory.CLASSES_KEY, CipherProvider.JCE.getClassName());
CryptoCipher decipher = Utils.getCipherInstance(transform, properties);
System.out.println("Cipher: " + encipher.getClass().getCanonicalName());
decipher.init(Cipher.DECRYPT_MODE, key, iv);
byte [] decoded = new byte[32];
decipher.doFinal(output, 0, updateBytes + finalBytes, decoded, 0);
System.out.println("output: " + new String(decoded, StandardCharsets.UTF_8));
}
/**
* Converts String to UTF8 bytes
*
* @param input the input string
* @return UTF8 bytes
*/
private static byte[] getUTF8Bytes(String input) {
return input.getBytes(StandardCharsets.UTF_8);
}
}
如果你对于OPENSSL比较抵触,那么可以考虑hack一下:
Hack一下
无论如何它是java,java我们就可以想到办法在运行时替换,所以解决思路就是通过反射把限制修改为没有限制,把下面的代码放到你需要调用的类上,这样在jvm加载这个类的同时会帮你修改相应的policy策略
代码如下:
static {
String errorString = "Failed manually overriding key-length permissions.";
int newMaxKeyLength;
try {
if ((newMaxKeyLength = Cipher.getMaxAllowedKeyLength("AES")) < 256) {
Class c = Class.forName("javax.crypto.CryptoAllPermissionCollection");
Constructor con = c.getDeclaredConstructor();
con.setAccessible(true);
Object allPermissionCollection = con.newInstance();
Field f = c.getDeclaredField("all_allowed");
f.setAccessible(true);
f.setBoolean(allPermissionCollection, true);
c = Class.forName("javax.crypto.CryptoPermissions");
con = c.getDeclaredConstructor();
con.setAccessible(true);
Object allPermissions = con.newInstance();
f = c.getDeclaredField("perms");
f.setAccessible(true);
((Map) f.get(allPermissions)).put("*", allPermissionCollection);
c = Class.forName("javax.crypto.JceSecurityManager");
f = c.getDeclaredField("defaultPolicy");
f.setAccessible(true);
Field mf = Field.class.getDeclaredField("modifiers");
mf.setAccessible(true);
mf.setInt(f, f.getModifiers() & ~Modifier.FINAL);
f.set(null, allPermissions);
newMaxKeyLength = Cipher.getMaxAllowedKeyLength("AES");
}
} catch (Exception e) {
throw new RuntimeException(errorString, e);
}
if (newMaxKeyLength < 256)
throw new RuntimeException(errorString); // hack failed
}