本文共 6530 字,大约阅读时间需要 21 分钟。
解决POI读取加密Excel文件时报的 EncryptedDocumentException: Export Restrictions in place错误,尝试了JCE和反射两种方法,猜想了一下JCE失败的原因。最后发现JCE是必须安装的,只是未必会生效,有条件。具体条件没有深究。
随便在网上搜一搜都会有这样功能的代码,贴在下面了。
public static XSSFWorkbook readExcel(String filePath, String password) throws IOException, GeneralSecurityException { File excelFile = new File(filePath); InputStream is = new FileInputStream(excelFile); XSSFWorkbook xssfWorkbook = null; POIFSFileSystem poifsFileSystem = new POIFSFileSystem(is); is.close(); EncryptionInfo encryptionInfo = new EncryptionInfo(poifsFileSystem); Decryptor decryptor = Decryptor.getInstance(encryptionInfo); boolean verifyPassword = decryptor.verifyPassword(password); if (!verifyPassword) { throw new SystemException("excel 密码错误!"); } xssfWorkbook = new XSSFWorkbook(decryptor.getDataStream(poifsFileSystem)); /* * try { xssfWorkbook=(XSSFWorkbook) WorkbookFactory.create(excelFile,password); * } catch (EncryptedDocumentException e) { * e.printStackTrace(); } catch (InvalidFormatException e) { * Auto-generated catch block e.printStackTrace(); } */ return xssfWorkbook; }
注释部分是POI自带的工具类,功能实现和方法里面一样。但是使用这段代码读取加密文件的时候同样会报错:
org.apache.poi.EncryptedDocumentException: Export Restrictions in place - please install JCE Unlimited Strength Jurisdiction Policy files at org.apache.poi.poifs.crypt.CryptoFunctions.getCipher(CryptoFunctions.java:226) at org.apache.poi.poifs.crypt.CryptoFunctions.getCipher(CryptoFunctions.java:200) at org.apache.poi.poifs.crypt.agile.AgileDecryptor.hashInput(AgileDecryptor.java:269) at org.apache.poi.poifs.crypt.agile.AgileDecryptor.verifyPassword(AgileDecryptor.java:114)
实际上是因为excel2013用的是256bit的SHA512去加密,但是java的Ciper功能会因为不同地区的要求受到不同的限制,恰巧有限制。所以只需要把限制放开就行了。
按照堆栈错误提示,很容易在网上找到解决步骤。首先下载安装的文件(,),根据提示,放到使用的jdk home目录下的jre/lib/security目录下。这样再执行上面的方法就行了。
很失望,我测试的结果是不行的。我有两个版本的jdk在电脑上,运行的是1.8,项目使用的是1.7。安装完成之后已经可以在项目中发现这个两个jar包了,但是仍然报同样的错误!
\img\jce_jar_in_project.png)
打开local_policy.jar文件,可以看到其中有两个文件:default_local.policy和exempt_local.policy。我的两个文件内容如下:
default_local.policy
// Some countries have import limits on crypto strength. This policy file// is worldwide importable.grant { permission javax.crypto.CryptoPermission "DES", 64; permission javax.crypto.CryptoPermission "DESede", *; permission javax.crypto.CryptoPermission "RC2", 128, "javax.crypto.spec.RC2ParameterSpec", 128; permission javax.crypto.CryptoPermission "RC4", 128; permission javax.crypto.CryptoPermission "RC5", 128, "javax.crypto.spec.RC5ParameterSpec", *, 12, *; permission javax.crypto.CryptoPermission "RSA", *; permission javax.crypto.CryptoPermission *, 128;};
exempt_local.policy
// Some countries have import limits on crypto strength. So this file// will be useful.grant { // There is no restriction to any algorithms if KeyRecovery is enforced. permission javax.crypto.CryptoPermission *, "KeyRecovery"; // There is no restriction to any algorithms if KeyEscrow is enforced. permission javax.crypto.CryptoPermission *, "KeyEscrow"; // There is no restriction to any algorithms if KeyWeakening is enforced. permission javax.crypto.CryptoPermission *, "KeyWeakening";};
另外还有一个文件:US_export_policy.jar,按注释的说明没有对加密有限制和要求了。内容如下:
US_export_policy.jar
// Manufacturing policy file.grant { // There is no restriction to any algorithms. permission javax.crypto.CryptoAllPermission; };
失败原因猜想:jdk是加载default_local.policy的策略,但是文件中并没有把256bite的方法加入permission。网上其他人说可以下载解决的,估计是default_local.policy有对应他们需要的加密算法。
从方案2.1中可以发现是因为jdk中的配置文件导致jdk对加密算法的秘钥长度有要求,如果能通过其他方法改掉这个限制就行了。实际上最后这个限制是用一个boolean字段标识,可以通过反射在解密前去除这个限制就行了。
在之前的工具类中增加下面的代码,在调用使用反射获取到标识字段,修改为false就行了。
static { removeCryptographyRestrictions(); } /** * 去掉加密秘钥长度的限制 */ 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) { log.error(e.getMessage(),e); } } 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); } /** * 判断环境是否需要去除加密限制 * @return */ 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")); }
运行解密方法,成功读取文件!
即使是使用2.2方案的这段代码,依然需要安装JCE