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

时间:2016-08-01 12:20:35

标签: java apache-poi testng sha256

使用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 example

感谢您的投入!

2 个答案:

答案 0 :(得分:2)

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

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

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

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

这不会影响XLS文件,因为HSSF格式不是“zipped 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);
}

答案 1 :(得分:1)

好的,我找到了一种重置所有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