Java POI XSSF/XLSX哈希不确定性与MessageDigest SHA-256

Java POI XSSF/XLSX哈希不确定性与MessageDigest SHA-256,java,apache-poi,testng,sha256,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 IOExce

在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");
}
试图消除由于元数据(如创建时间)造成的不确定性,但无济于事:

// 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本身没有bug,有人知道是什么导致了这一点吗?基本上,上面的代码是指


谢谢你的意见

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

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

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

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

这不会影响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);
}
ZipFile文件=新的ZipFile(pathToFile);
枚举e=file.entries();
而(e.hasMoreElements()){
ZipEntry entry=e.nextElement();
输入设置时间(0L);
}

好的,根据SO的一些示例,我找到了一种重置所有XSLX文件条目文件时间属性的方法。不幸的是,只有文件条目似乎可以通过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);
        }
    }
}
private void resetOPCPTimeAttributes(文件)
抛出InvalidFormatException、IOException、OpenXML4JEException、XmlException{
OPCPackage opcp=ZipPackage.open(文件);
resetZipfileContentTimeAttributes(opcp.getParts());
opcp.flush();
opcp.close();
}
private void resetZipfileContentTimeAttributes(列表部分)引发InvalidFormatException{
ArrayList子部分=null;
用于(包装零件:零件){
PackageProperties props=part.getPackage().getPackageProperties();
props.setLastModifiedByProperty(“”);
props.setCreatedProperty(“”);
props.setModifiedProperty(“”);
子部件=part.getPackage().getParts();
while(子部分!=null){
resetZipfileContentTimeAttributes(子部分);
}
}
}
编辑:


同时(直到我或其他人找到一个在Zip存档中处理文件夹元数据的解决方案),我已经切换到这里的深度比较解决方案:

感谢您的想法。我必须测试并编写实际的文件以进行双重检查。但是设置CoreProperty元数据(如上所述的创建和修改时间)不应该防止这种情况吗?或者它只影响内部元数据,而不影响归档文件的元数据?我认为检查这一点的最佳方法如您所说:编写文件并检查zip内容。在我现有的文件中,我没有修改CoreProterties,所以我不知道这是否是造成我的情况不同的原因。看起来你让我走上了正确的道路,皮特!我提取了生成的预期内容和实际内容,所有内容都很相似,包括文件、文件夹、CRC,但修改时间相差2秒。事实上,我已经明确告诉POI清除修改时间,这很奇怪。除非,否则这会影响其他内部修改时间。现在我只需要弄清楚,如何在创建之前或之后操纵XLSX中文件的修改时间。否则,我看不到其他方法,只能解压缩、触摸并重新压缩文件。@fozzybear您的代码告诉POI更改文档级元数据,如果您检查文档属性,这将显示在Office中。它不影响由低级zip自动设置的zip级元数据library@Gagravarr是的,我已经弄明白了,但是需要找到一种方法来更改zip内容的属性。我想我找到了一个可能的解决办法
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);
        }
    }
}