現在對檔案的完整性驗證,防止檔案被篡改的技術已經比較成熟,一般使用數字簽名,數字水印等,最近我在一個項目中也遇到了防篡改的需求。該項目要求使用者将原始發票用專門的掃描程式掃描成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
37
/// 對給定路徑的檔案進行驗證
38
39
/// <param name="path"></param>
40
/// <returns>是否加了标簽或是否标簽值與内容值一緻</returns>
41
public static bool check(string path,string key)
42
43
44
45
46
47
48
49
get_file.read(pdffile, 0, (int)get_file.length);
50
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
}