現在對文件的完整性驗證,防止文件被篡改的技術已經比較成熟,一般使用數字簽名,數字水印等,最近我在一個項目中也遇到了防篡改的需求。該項目要求用戶將原始發票用專門的掃描程序掃描成pdf文件,然後將該pdf文件傳到服務器上,在上傳的同時必須要驗證這個pdf是沒有被手工修改過的。我剛一接觸到這個需求想到的就是使用數字水印,要不然就直接使用PDF的數字簽名功能,不過這些方法都感覺比較比較復雜,一大堆的英文文檔也沒有心思去研究,於是琢磨了半天,寫了一個簡化版的數字水印程序,實現了pdf文件完整性驗證。
驗證的基本思路是:
對文件全部內容計算其MD5值,這樣無論用戶修改了文件的任何一個地方,那麼生成的MD5的是完全不一樣的,我們可以將這個MD5寫到文件的一個隱藏區,一般二進制文件格式都有文件頭和文件體部分,而文件頭是用戶看不到的,一般也會預留一部分字節用於以後擴展,或可以在文件頭寫入特殊標記的數據。於是研究了一下pdf文件的格式,試著往其第10個字節插入了MD5值,結果文件雖然可以使用,但是每次打開的時候都會提示“文件修復”。原來是寫在頭上面的內容將pdf文件的字節數和文件中對象的地址改變了,導致了文件錯誤,原因找到了那麼解決辦法也就有了,為了不改變pdf文件中對象的地址,那麼我們將這個md5寫在文件尾不就可以了嘛!於是在客戶端(掃描程序)將掃描出的pdf文件流計算MD5值,然後將該文件流和MD5值一起寫到硬盤上,形成一個添加了MD5值的pdf文件。文件可以正常打開和使用,而且用戶也不會看到我們添加的這個MD5值。
在服務器端,我們將上傳上來的文件流除了最後32個字節以為的部分計算MD5值(這兒取32個字節是因為最後這32字節是我們寫的MD5),將前面部分算出的MD5和最後32個字節的MD5進行比較,如果一樣那麼說明這個文件從掃描程序生成以後沒有被人為篡改過,否則說明該文件要麼不是用我們這個掃描程序生成的要麼就是被篡改了。這樣驗證通過以後我們才將該文件流寫到服務器硬盤上。
相關程序代碼
1 public class MD5
2 {
3 /**//// <summary>
4 /// 對給定文件路徑的文件加上標簽
5 /// </summary>
6 /// <param name="path">要加密的文件的路徑</param>
7 /// <returns>標簽的值</returns>
8 public static string MD5pdf(string path,string key)
9 {
10
11 try
12 {
13 FileStream get_file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
14 byte[] pdfFile = new byte[get_file.Length];
15 get_file.Read(pdfFile, 0, (int)get_file.Length);//將文件流讀取到Buffer中
16 get_file.Close();
17
18 string result = MD5Buffer(pdfFile, 0, pdfFile.Length );//對Buffer中的字節內容算MD5
19 result = MD5String(result +key);//這兒點的key相當於一個密鑰,這樣一般人就是知道使用MD5算法,但是若不知道這個字符串還是無法計算出正確的MD5
20
21 byte[] md5 = System.Text.Encoding.ASCII.GetBytes(result);//將字符串轉換成字節數組以便寫人到文件中
22
23 FileStream fsWrite = new FileStream(path, FileMode.Open, FileAccess.ReadWrite);
24 fsWrite.Write(pdfFile, 0, pdfFile.Length);//將pdf文件,MD5值 重新寫入到文件中。
25 fsWrite.Write(md5, 0, md5.Length);
26 //fsWrite.Write(pdfFile, 10, pdfFile.Length - 10);
27 fsWrite.Close();
28
29 return result;
30 }
31 catch (Exception e)
32 {
33 return e.ToString();
34 }
35 }
36 /**//// <summary>
37 /// 對給定路徑的文件進行驗證
38 /// </summary>
39 /// <param name="path"></param>
40 /// <returns>是否加了標簽或是否標簽值與內容值一致</returns>
41 public static bool Check(string path,string key)
42 {
43 try
44 {
45 FileStream get_file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
46
47
48 byte[] pdfFile = new byte[get_file.Length];
49 get_file.Read(pdfFile, 0, (int)get_file.Length);
50 get_file.Close();
51 string result = MD5Buffer(pdfFile, 0, pdfFile.Length - 32);//對pdf文件除最後32位以外的字節計算MD5,這個32是因為標簽位為32位。
52 result = MD5String(result + key);
53
54 string md5 = System.Text.Encoding.ASCII.GetString(pdfFile, pdfFile.Length - 32, 32);//讀取pdf文件最後32位,其中保存的就是MD5值
55 return result == md5;
56 }
57 catch
58 {
59
60 return false;
61
62 }
63 }
64 private static string MD5Buffer(byte[] pdfFile, int index, int count)
65 {
66 System.Security.Cryptography.MD5CryptoServiceProvider get_md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
67 byte[] hash_byte = get_md5.ComputeHash(pdfFile, index, count);
68
69 string result = System.BitConverter.ToString(hash_byte);
70 result = result.Replace("-", "");
71 return result;
72 }
73 private static string MD5String(string str)
74 {
75 byte[] MD5Source = System.Text.Encoding.ASCII.GetBytes(str);
76 return MD5Buffer(MD5Source, 0, MD5Source.Length);
77
78 }
79 }
以上代碼不僅僅只適用於PDF文件,對於其他一些格式也可以用,這主要是取決於文件的格式規范。