【问题标题】:POI XSSF / XLSX hashing indeterminism with MessageDigest SHA-256使用 MessageDigest SHA-256 的 POI XSSF / XLSX 散列不确定性
【发布时间】:2018-08-18 19:05:24
【问题描述】:

使用 MessageDigest SHA-256 实现获取 POI XLSX 格式的确定性哈希值似乎存在问题,即使对于空的 ByteArray 流也是如此。这是随机发生的,经过数百次甚至数千次迭代。

用于重现问题的相关代码sn-ps:

// TestNG FileTest:
@Test(enabled = true) // indeterminism at random iterations, such as 400 or 1290
public void emptyXLSXTest() throws IOException, NoSuchAlgorithmException {
    final Hasher hasher = new HasherImpl();
    boolean differentSHA256Hash = false;
    for (int i = 0; i < 10000; i++) {
        final ByteArrayOutputStream excelAdHoc1 = BusinessPlanInMemory.getEmptyExcel("xlsx");
        final ByteArrayOutputStream excelAdHoc2 = BusinessPlanInMemory.getEmptyExcel("xlsx");

        byte[] expectedByteArray = excelAdHoc1.toByteArray();
String expectedSha256 = hasher.sha256(expectedByteArray);
byte[] actualByteArray = excelAdHoc2.toByteArray();
String actualSha256 = hasher.sha256(actualByteArray);

if (!expectedSha256.equals(actualSha256)) {
            differentSHA256Hash = true;
            System.out.println("ITERATION: " + i);
            System.out.println("EXPECTED HASH: " + expectedSha256);
            System.out.println("ACTUAL HASH: " + actualSha256);
            break;
        }
    }
    Assert.assertTrue(differentSHA256Hash, "Indeterminism did not occur");
}

引用的Hasher和POI代码:

// HasherImpl class:
public String sha256(final InputStream stream) throws IOException, NoSuchAlgorithmException {
    final MessageDigest digest = MessageDigest.getInstance("SHA-256");
    final byte[] bytesBuffer = new byte[300000]; 
    int bytesRead = -1;
    while ((bytesRead = stream.read(bytesBuffer)) != -1) {
        digest.update(bytesBuffer, 0, bytesRead);
    }
    final byte[] hashedBytes = digest.digest();
    return bytesToHex(hashedBytes);
}

试图消除元数据(如创建时间)造成的不确定性,但无济于事:

// POI BusinessPlanInMemory helper class:
public static ByteArrayOutputStream getEmptyExcel(final String fileextension) throws IOException {
    Workbook wb;

    if (fileextension.equals("xls")) {
        wb = new HSSFWorkbook();
    }
    else {
        wb = new XSSFWorkbook();
        final POIXMLProperties props = ((XSSFWorkbook) wb).getProperties();
        final POIXMLProperties.CoreProperties coreProp = props.getCoreProperties();
        coreProp.setCreated("");
        coreProp.setIdentifier("1");
        coreProp.setModified("");
    }

    wb.createSheet();

    final ByteArrayOutputStream excelStream = new ByteArrayOutputStream();
    wb.write(excelStream);
    wb.close();
    return excelStream;
}

HSSF / XLS 格式似乎不受所述问题的影响。 有没有人知道,如果不是 POI 本身的错误,可能是什么原因造成的?基本上,上面的代码是指 https://poi.apache.org/spreadsheet/examples.htmlBusinessPlan example

感谢您的意见!

【问题讨论】:

    标签: java apache-poi testng sha256


    【解决方案1】:

    这不是一个确定的答案,但我怀疑会发生什么:

    docx 和 xlsx 文件格式基本上是一堆压缩的 xml 文件。将它们重命名为 .zip 并使用您最喜欢的 zip 工具打开时,很容易看到这一点。

    在检查由 word 创建的文件时,我注意到存档中包含的所有文件的更改时间戳始终为 1980-01-01 00:00:00,而在使用 POI 创建的文件中,它将显示文件创建的实际时间戳。

    所以我怀疑当excelAdHoc1excelAdHoc2 中的一个或多个文件之间存在时间戳差异时会出现您的问题。当创建一个或另一个文件时时钟切换到下一秒时,可能会发生这种情况。

    这不会影响 XLS 文件,因为 HSSF 格式不是“压缩 xml”类型,因此不包含任何可能具有不同时间戳的嵌套文件。

    要在写入文件后更改时间戳,您可以尝试使用 `java.util.zip``-package。我还没有测试过,但这应该可以解决问题:

    ZipFile file = new ZipFile(pathToFile);
    Enumeration<ZipEntry> e = file.entries();
    while(e.hasMoreElements()) {
        ZipEntry entry = e.nextElement();
        entry.setTime(0L);
    }
    

    【讨论】:

    • 感谢您对此的看法。我将不得不测试并编写实际文件来仔细检查它。但是不应该设置 CoreProperty 元数据(创建和修改时间,如上所述)防止这种情况发生吗?还是它只影响内部元数据,而不影响存档的元数据?
    • 我认为检查这一点的最佳方法是如您所说:编写文件并检查 zip 内容。在我现有的文件中,我没有修改 CoreProterties,所以我不知道这是否是导致我的情况不同的原因。
    • 看起来你让我走上了正轨,皮特!我提取了生成的预期内容和实际内容,一切都相似,文件、文件夹、CRC,但修改时间相差 2 秒。鉴于我已经明确告诉 POI 清除修改时间,这很奇怪。除非这会影响其他内部修改时间。现在我只需要弄清楚,如何在创建之前或之后操作 XLSX 中文件的修改时间。否则,我看不到其他方法,只能解压缩,触摸并重新压缩文件。你怎么看?
    • @fozzybear 您的代码告诉 POI 更改文档级元数据,如果您检查文档属性,这就是在 Office 中显示的内容。它不影响 zip 级别的元数据,它由低级 zip 库自动设置
    • @Gagravarr 是的,我已经想通了,但需要找到一种方法来更改 zip 内容的属性。认为我在这里找到了可能的解决方案link
    【解决方案2】:

    好的,我找到了一种重置所有 XSLX 文件条目文件时间属性的方法,根据在 SO 中找到的一些示例。不幸的是,似乎只有文件条目可以通过 ZipFile 或 OPCPackage 等方法访问。我找不到解决方案来访问和重置存档中的文件夹,这些文件夹也具有不同的时间属性。

    到目前为止,我没有成功消除 POI 生成的 XLSX 存档的不同属性,以便从两个其他相同的文件中获取相同的 SHA256 哈希,这似乎是不同属性的原因。

    private void resetOPCPTimeAttributes(File file)
            throws InvalidFormatException, IOException, OpenXML4JException, XmlException {
    
        OPCPackage opcp = ZipPackage.open(file);
        resetZipfileContentTimeAttributes(opcp.getParts());
    
        opcp.flush();
        opcp.close();
    }
    
    private void resetZipfileContentTimeAttributes(List<PackagePart> parts) throws InvalidFormatException {
    
        ArrayList<PackagePart> subParts = null;
        for (PackagePart part: parts) {
    
            PackageProperties props = part.getPackage().getPackageProperties();
            props.setLastModifiedByProperty("");
            props.setCreatedProperty("");
            props.setModifiedProperty("");
    
            subParts = part.getPackage().getParts();
    
            while (subParts != null) {
                resetZipfileContentTimeAttributes(subParts);
            }
        }
    }
    

    编辑:

    与此同时(直到我或其他人找到处理 Zip 存档中文件夹元数据的解决方案),我已在此处切换到深度比较解决方案:Comparing XLSX files

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-06-04
      • 2011-06-28
      • 2018-07-14
      • 1970-01-01
      • 2012-05-30
      • 2021-02-10
      • 2017-02-13
      • 2011-05-18
      相关资源
      最近更新 更多