Android中使用Apache POI解密Excel文件失败求助
疫情期间祝大家身体健康!我在Android平台用Apache POI搞定了Excel文件加密,但解密环节一直卡壳,报错说无法解析加密描述符,恳请各位大佬帮忙看看问题出在哪。加密用的密码是password。
我的代码与报错信息
加密代码
public void encryptXLSX() throws IOException, GeneralSecurityException, InvalidFormatException { // input is unprotected excel named as excel.xlsx String input = "/storage/emulated/0/Android/data/com.protect.excel_protect_example/files/excel.xlsx"; /// output file path String outputPath = "/storage/emulated/0/Android/data/com.protect.excel_protect_example/files/protected_excel.xlsx"; POIFSFileSystem fs = new POIFSFileSystem(); Biff8EncryptionKey.setCurrentUserPassword("password"); EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile); Encryptor enc = info.getEncryptor(); enc.confirmPassword("password"); InputStream fis = new FileInputStream(input); try (OPCPackage opc = OPCPackage.open(fis); OutputStream os = enc.getDataStream(fs)) { opc.save(os); os.close(); } FileOutputStream fos1 = new FileOutputStream(outputPath); fs.writeFilesystem(fos1); fos1.close(); fs.close(); fis.close(); }
解密代码
public boolean isEncrypted(String path) { try { try { new POIFSFileSystem(new FileInputStream(path)); } catch (IOException ignored) { } System.out.println("protected"); return true; } catch (OfficeXmlFileException e) { System.out.println("not protected"); return false; } } public byte[] decryptXLSX() throws Exception { String sourcepath = "/storage/emulated/0/Android/data/com.protect.excel_protect_example/files/protected_excel.xlsx"; InputStream in = null; FileInputStream fis = new FileInputStream(sourcepath); if (isEncrypted(sourcepath)) { org.apache.poi.hssf.record.crypto.Biff8EncryptionKey.setCurrentUserPassword(password); POIFSFileSystem filesystem = new POIFSFileSystem(fis); print("Header Block:" + filesystem.getHeaderBlock().toString()); print("property tables:" + filesystem.getRoot().getEntries().toString()); EncryptionInfo info = new EncryptionInfo(filesystem); //EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile); //EncryptionInfo info = new EncryptionInfo(filesystem.getRoot().createDocumentInputStream("EncryptionInfo"), EncryptionMode.agile); //EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile, CipherAlgorithm.aes256, HashAlgorithm.sha512, 256, 16, ChainingMode.cbc); Decryptor d = Decryptor.getInstance(info); if (!d.verifyPassword("password")) { print("Wrong password"); } else { print("Good!"); } in = d.getDataStream(filesystem); } else { in = new FileInputStream(sourcepath); } ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[1024]; while ((nRead = in.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); byte[] byteArray = buffer.toByteArray(); FileOutputStream fos1 = new FileOutputStream( "/storage/emulated/0/Android/data/com.protect.excel_protect_example/files/Unprotected_excel.xlsx"); fos1.write(byteArray); fos1.close(); return byteArray; }
报错信息
org.apache.poi.EncryptedDocumentException: Unable to parse encryption descriptor
W/System.err(28825): at org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder.parseDescriptor(AgileEncryptionInfoBuilder.java:106)
W/System.err(28825): at org.apache.poi.poifs.crypt.agile.AgileEncryptionInfoBuilder.initialize(AgileEncryptionInfoBuilder.java:40)
W/System.err(28825): at org.apache.poi.poifs.crypt.EncryptionInfo.(EncryptionInfo.java:152)
W/System.err(28825): at org.apache.poi.poifs.crypt.EncryptionInfo.(EncryptionInfo.java:101)
W/System.err(28825): at org.apache.poi.poifs.crypt.EncryptionInfo.(EncryptionInfo.java:94)
W/System.err(28825): at com.protect.excel_protect.decryptXLSX(ExcelProtect.java:182)
错误就出在解密函数第182行的EncryptionInfo info = new EncryptionInfo(filesystem);这一句。
问题分析与修复方案
1. 移除加密代码中的冗余操作
你加密时调用了Biff8EncryptionKey.setCurrentUserPassword("password");,这是给旧版.xls(HSSF格式)文件设置密码用的,而你处理的是.xlsx(XSSF/OPCPackage),这行代码完全多余,甚至可能干扰加密流程,先把这行删掉,重新生成加密文件。
2. 修复加密文件判断逻辑
你的isEncrypted方法逻辑太粗糙:捕获IOException后直接忽略并返回true,会把文件损坏、权限问题等情况误判成加密文件。建议替换为POI官方提供的准确判断方法:
public boolean isEncrypted(String path) { try (FileInputStream fis = new FileInputStream(path)) { return EncryptionInfo.isEncrypted(fis); } catch (IOException e) { e.printStackTrace(); return false; } }
该方法会准确识别加密状态,同时用try-with-resources自动关闭流,避免资源泄漏。
3. 修正解密时的流处理错误
你在解密时,先调用isEncrypted(sourcepath)已经打开了一个文件流但未关闭,后续再打开fis时,文件指针可能已到末尾,导致POIFSFileSystem读取不到正确的加密信息。
修改解密代码,确保使用全新的未读取流,移除针对HSSF的冗余密码设置,并用try-with-resources自动管理流:
public byte[] decryptXLSX() throws Exception { String sourcepath = "/storage/emulated/0/Android/data/com.protect.excel_protect_example/files/protected_excel.xlsx"; InputStream in = null; try (FileInputStream fis = new FileInputStream(sourcepath)) { if (isEncrypted(sourcepath)) { // 移除冗余的HSSF密码设置 // org.apache.poi.hssf.record.crypto.Biff8EncryptionKey.setCurrentUserPassword(password); POIFSFileSystem filesystem = new POIFSFileSystem(fis); print("Header Block:" + filesystem.getHeaderBlock().toString()); print("property tables:" + filesystem.getRoot().getEntries().toString()); EncryptionInfo info = new EncryptionInfo(filesystem); Decryptor d = Decryptor.getInstance(info); if (!d.verifyPassword("password")) { print("Wrong password"); } else { print("Good!"); } in = d.getDataStream(filesystem); } else { in = new FileInputStream(sourcepath); } ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int nRead; byte[] data = new byte[1024]; while ((nRead = in.read(data, 0, data.length)) != -1) { buffer.write(data, 0, nRead); } buffer.flush(); byte[] byteArray = buffer.toByteArray(); try (FileOutputStream fos1 = new FileOutputStream( "/storage/emulated/0/Android/data/com.protect.excel_protect_example/files/Unprotected_excel.xlsx")) { fos1.write(byteArray); } return byteArray; } finally { if (in != null) { in.close(); } } }
4. 确认POI版本兼容性
建议使用Apache POI 4.1.2及以上的稳定版,不同版本的加密解密逻辑可能存在差异,老版本可能存在已知bug,升级到新版能避免很多兼容性问题。
按照以上步骤修改后,应该就能正常解密了。
内容的提问来源于stack exchange,提问作者group work




