2016-11-30 84 views
2

我們有一個內部Web應用程序,它接受來自用戶的各種格式的文件,以便將大量數據導入到我們的系統中。比較具有相同內容的XLSX文件之間的MD5哈希

我們實現的最新升級之一是添加一種方法來檢測文件是否先前已上傳,如果是,向用戶顯示警告和選項以重新提交文件或取消上傳。

爲了達到這個目的,我們正在計算上傳文件的MD5,並將它與包含以前上傳的文件信息的數據庫表進行比較,以確定它是否是重複的。如果在MD5上有匹配,則顯示警告,否則它將新的文件信息插入表中並繼續進行文件處理。

下面是用來生成MD5哈希C#代碼:

private static string GetHash(byte[] input) 
{ 
    using (MD5 md5 = MD5.Create()) 
    { 
     byte[] data = md5.ComputeHash(input); 

     StringBuilder bob = new StringBuilder(); 

     for (int i = 0; i < data.Length; i++) 
      bob.Append(data[i].ToString("x2").ToUpper()); 

     return bob.ToString(); 
    } 
} 

一切正常嗯...有一個例外。

允許用戶上傳.xlsx文件進行此過程,不幸的是,此文件類型還將文件的元數據存儲在文件內容中。 (這可以很容易通過改變.xlsx文件的擴展名的.zip並提取內容可以看出[參見下文]。)

Excel Metadata

正因爲如此,該MD5哈希.xlsx文件將與改變每次後續保存,即使文件內容相同(只需打開並保存文件,不進行修改將刷新元數據並導致不同的散列)。

在這種情況下,具有相同記錄但在不同時間或由不同用戶創建的文件將滑過重複文件檢測並進行處理。

我的問題:有沒有辦法來確定是否.xlsx文件的內容匹配是以前的文件沒有存儲文件的內容?換句話說:有沒有一種方法可以產生一個.xlsx文件的內容MD5散列?

回答

1

在計算散列值之前,您可以從文檔中刪除不應影響散列值的部分。

這可以通過將Open XML包的所有部分提取到單個XML文檔中,刪除不需要的節點並計算生成的XML文檔的散列來實現。請注意,您必須重新計算已上傳的Excel文件的哈希值才能正常工作,因爲哈希現在不再基於二進制文件內容。

下面是一個簡單的示例程序(添加引用WindowsBase.dll中):

using System; 
using System.IO; 
using System.IO.Packaging; 
using System.Linq; 
using System.Security.Cryptography; 
using System.Text; 
using System.Xml.Linq; 

internal class Program 
{ 
    private static readonly XNamespace dcterms = "http://purl.org/dc/terms/"; 

    private static void Main(string[] args) 
    { 
     var fileName = args[0]; 

     // open the ZIP package 
     var package = Package.Open(fileName); 

     // convert the package to a single XML document 
     var xdoc = OpcToFlatOpc(package); 

     // remove the nodes we are not interested in 
     // here you can add other nodes as well 
     xdoc.Descendants(dcterms + "modified").Remove(); 

     // get a stream of the XML and compute the hash 
     using (var ms = new MemoryStream()) 
     { 
      xdoc.Save(ms); 
      ms.Position = 0; 

      string md5 = GetHash(ms); 
      Console.WriteLine(md5); 
     } 
    } 

    private static string GetHash(Stream stream) 
    { 
     using (var md5 = MD5.Create()) 
     { 
      var data = md5.ComputeHash(stream); 

      var bob = new StringBuilder(); 

      for (int i = 0; i < data.Length; i++) 
      { 
       bob.Append(data[i].ToString("X2")); 
      } 

      return bob.ToString(); 
     } 
    } 

    private static XDocument OpcToFlatOpc(Package package) 
    { 
     XNamespace pkg = "http://schemas.microsoft.com/office/2006/xmlPackage"; 
     var declaration = new XDeclaration("1.0", "UTF-8", "yes"); 
     var doc = new XDocument(
      declaration, 
      new XProcessingInstruction("mso-application", "progid=\"Word.Document\""), 
      new XElement(
       pkg + "package", 
       new XAttribute(XNamespace.Xmlns + "pkg", pkg.ToString()), 
       package.GetParts().Select(GetContentsAsXml))); 

     return doc; 
    } 

    private static XElement GetContentsAsXml(PackagePart part) 
    { 
     XNamespace pkg = "http://schemas.microsoft.com/office/2006/xmlPackage"; 
     if (part.ContentType.EndsWith("xml")) 
     { 
      using (var partstream = part.GetStream()) 
      { 
       using (var streamReader = new StreamReader(partstream)) 
       { 
        string streamString = streamReader.ReadToEnd(); 
        if (!string.IsNullOrEmpty(streamString)) 
        { 
         var newXElement = 
         new XElement(
          pkg + "part", 
          new XAttribute(pkg + "name", part.Uri), 
          new XAttribute(pkg + "contentType", part.ContentType), 
          new XElement(pkg 
          + "xmlData", XElement.Parse(streamString))); 
         return newXElement; 
        } 

        return null; 
       } 
      } 
     } 

     using (var str = part.GetStream()) 
     { 
      using (var binaryReader = new BinaryReader(str)) 
      { 
       int len = (int)binaryReader.BaseStream.Length; 
       var byteArray = binaryReader.ReadBytes(len); 

       // the following expression creates the base64String, then chunks 
       // it to lines of 76 characters long 
       string base64String = Convert.ToBase64String(byteArray) 
         .Select((c, i) => new { Character = c, Chunk = i/76 }) 
         .GroupBy(c => c.Chunk) 
         .Aggregate(
          new StringBuilder(), 
          (s, i) => 
           s.Append(
            i.Aggregate(
             new StringBuilder(), 
             (seed, it) => seed.Append(it.Character), 
             sb => sb.ToString())) 
           .Append(Environment.NewLine), 
          s => s.ToString()); 

       return new XElement(
        pkg + "part", 
        new XAttribute(pkg + "name", part.Uri), 
        new XAttribute(pkg + "contentType", part.ContentType), 
        new XAttribute(pkg + "compression", "store"), 
        new XElement(pkg + "binaryData", base64String)); 
      } 
     } 
    } 
} 
+0

謝謝你 - 這是肯定的東西,我可以工作。我有點擔心潛在的開銷,但從我用過的少數測試案例來看,它似乎運作良好。 – Siyual