使用MessageDigest SHA-256的POI XSSF / XLSX散列不确定性

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

用于重现问题的相关代码段:

// 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.html BusinessPlan示例

感谢您的输入!

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

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

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

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

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

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

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

好的,我已经找到了一种方法来重置所有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 parts) throws InvalidFormatException { ArrayList 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存档中操作文件夹元数据的解决方案),我已经切换到这里的深度比较解决方案: 比较XLSX文件

XSSFWorkbook.getCTWorkbook()怎么样? 如果XML内容没有改变,它似乎总是产生相同的哈希。