2016-08-01 30 views
3

Es scheint ein Problem mit deterministischen Hash-Werte für das POI XLSX-Format mit MessageDigest SHA-256-Implementierung, auch für leere ByteArray-Streams. Dies geschieht zufällig nach mehreren hundert oder sogar nur tausenden von Iterationen.POI XSSF/XLSX Hash-Indeterminismus mit MessageDigest SHA-256

Der entsprechende Code-Schnipsel verwendet, um das Problem zu reproduzieren:

// 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"); 
} 

referenzierten Hasher und POI-Code:

// 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); 
} 

Versuchte indeterminism aufgrund Meta-Daten wie Erstellungszeit zu eliminieren, ohne Erfolg:

// 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; 
} 

Das HSSF/XLS-Format scheint nicht von dem Profi betroffen zu sein beschämt beschrieben. Hat jemand eine Ahnung, was könnte das verursachen, wenn nicht ein Fehler in POI selbst? Grundsätzlich bezieht sich der obige Code auf https://poi.apache.org/spreadsheet/examples.htmlBusinessPlan example

Vielen Dank für Ihre Eingabe! docx und xlsx Dateiformate sind im Grunde eine Reihe von Reißverschluss-up xml-Dateien

:

Antwort

2

Dies keine definitive Antwort, aber das ist meine Vermutung, was passiert. Dies kann leicht gesehen werden, wenn Sie sie in .zip umbenennen und mit Ihrem bevorzugten Zip-Tool öffnen.

Beim Überprüfen einer mit Word erstellten Datei habe ich festgestellt, dass der Änderungszeitstempel aller Dateien im Archiv immer 1980-01-01 00:00:00 ist, während in den mit POI erstellten Dateien der tatsächliche Zeitstempel der Datei angezeigt wird.

Also ich vermute, dass Ihr Problem auftritt, wenn es einen Zeitstempel-Unterschied zwischen einer oder mehreren Dateien in excelAdHoc1 und excelAdHoc2 gibt. Dies kann passieren, wenn die Uhr beim Erstellen der einen oder anderen Datei zur nächsten Sekunde wechselt.

Dies hat keine Auswirkungen auf XLS-Dateien, da das HSSF-Format nicht vom "zipped xml" -Typ ist und daher keine verschachtelten Dateien enthält, die unterschiedliche Zeitstempel haben könnten.

Um die Zeitstempel nach dem Schreiben der Datei zu ändern, können Sie das `java.util.zip``-Paket verwenden. Ich habe es nicht getestet, aber das sollte es tun:

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

Danke für Ihre Gedanken dazu. Ich muss die tatsächlichen Dateien testen und schreiben, um sie zu überprüfen. Aber sollte das Setzen der CoreProperty-Metadaten (wie oben beschrieben) nicht eine Situation wie diese verhindern? Oder betrifft das nur interne Metadaten, nicht die des Archivs? – fozzybear

+0

Ich denke, der beste Weg, dies zu überprüfen, wäre wie gesagt: schreibe die Datei und überprüfe die ZIP-Inhalte. In meinen vorhandenen Dateien hatte ich die CoreProterties nicht verändert, daher kann ich nicht sagen, ob das in meinem Fall den Unterschied verursacht. –

+0

Sieht aus, als hättest du mich auf den richtigen Weg gebracht, Piet! Ich habe die generierten erwarteten und tatsächlichen Inhalte extrahiert, und alles war ähnlich, Dateien, Ordner, CRCs, aber die Änderungszeiten unterschieden sich um 2 Sekunden. Angesichts der Tatsache, dass ich POI ausdrücklich gesagt habe, die Modifizierungszeiten zu löschen, ist das merkwürdig. Es sei denn, dies wirkt sich auf andere interne Modifikationszeiten aus. Jetzt muss ich nur herausfinden, wie man die Änderungszeiten der Dateien innerhalb des XLSX vor oder nach der Erstellung manipuliert. Ansonsten sehe ich keinen anderen Weg, als die Dateien zu entpacken, zu berühren und neu zu zippen. Was denken Sie? – fozzybear

1

Ok, ich habe einen Weg gefunden, alle XSLX Dateieintrag Datei Zeitattribute, nach einigen Beispiel hier bei SO gefunden zurückgesetzt werden. Leider scheinen nur Dateieinträge durch Methoden wie ZipFile oder OPCPackage zugänglich zu sein. Ich konnte keine Lösung finden, um auch die Ordner innerhalb des Archivs aufzurufen und zurückzusetzen, die auch unterschiedliche Zeitattribute haben.

Bisher ist es mir nicht gelungen, die unterschiedlichen Eigenschaften der POI-generierten XLSX-Archive zu eliminieren, um aus zwei ansonsten identischen Dateien dieselben SHA256-Hashes zu erhalten, für die die unterschiedlichen Attribute der Grund zu sein scheinen.

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); 
     } 
    } 
} 

Edit:

In der Zwischenzeit (bis finde ich oder jemand anderes eine Lösung Ordner Meta-Daten innerhalb des Zip-Archiv für die Manipulation), ich habe hier in die Tiefe zu vergleichen Lösung eingeschaltet: Comparing XLSX files