You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android中使用Apache POI解密Excel文件失败求助

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

火山引擎 最新活动