真正弄懂md5值是什麼

今天寫了一個程序,掃描指定目錄,遍歷這個目錄及子目錄下全部文件,生成每一個文件的md5值,記錄到一個字典變數,然後在這堆記錄裡面找出重複的文件。

之前看廖雪峰老師博客中摘要演算法簡介這篇文章,他把md5值講得很透徹,但我理解起來一直有一些朦朦朧朧的疑惑沒有解開,把這個程序測試通過後,我感覺才真正搞懂了md5值,於是有了這篇文章。

首先,介紹一下我寫的這個程序。

1、遞歸遍歷給定目錄下的全部文件,將文件路徑存入到一個列表。

2、利用hashlib.md5函數計算每一個有文件的md5值,將文件路徑和md5值,存儲到字典變數md5_dict。字典變數的結構是{「文件路徑」:「文件的md5值」},注意字典里存放的是文件的md5值,不是文件所在路徑的md5值。

3、每生成一個文件的md5值出來後,先到字典變數dup_dict裡面去匹配,如果md5值匹配成功,說明文件內容存在重複,那麼將這個出現重複的記錄寫入字典變數dup_dict。

生成文件路徑的函數dirlist如下。

def dirlist(path, allfile, shortcutfile): filelist = os.listdir(path) ? for filename in filelist: # 排除隱藏文件 if not filename.startswith(.): filepath = os.path.join(path, filename) if os.path.isdir(filepath): # 如果是folder,調用函數自身 dirlist(filepath, allfile, shortcutfile) elif os.path.isfile(filepath): # 如果是file,將文件路徑存入allfile列表 allfile.append(filepath) else: # 遍歷時如果既不是folder也不是file,那麼很可能就是替身變數, # 將文件路徑存入shortcutfile列表 # macOS裡面的替身變數,你可以理解成Windows裡面的快捷方式 shortcutfile.append(filepath) else: filepath = os.path.join(path, filename) if os.path.isdir(filename): print "%s is a hidden folder" %filepath else: print "%s is a hidden file" %filepath? return allfile, shortcutfile

計算md5值的函數gen_md5如下。

def gen_md5(file_list): for file_path in file_list:? #得到文件屬性 statinfo=os.stat(file_path) #文件大小 sizefile=statinfo.st_size #創建時間 createtime=formattime(statinfo.st_ctime) #修改時間 changetime=formattime(statinfo.st_mtime) #瀏覽時間 readtime=formattime(statinfo.st_atime)? # 給小於20M的文件生成md5值 if 0 < sizefile <= 20000000:? with open(file_path, rb) as fp: md5_value = hashlib.md5(fp.read()).hexdigest()? if md5_value in md5_dict.values(): dup_dict[file_path] = md5_value else: md5_dict[file_path] = md5_value

運行結果出來後,我檢查dup_dict字典里的記錄發現「365c9bfeb7d89244f2ce01c1de44cb85」這個md5值出現了三次,鍵值對記錄如下。

/Users/jacksonshawn/PythonCodes/pythonlearning/flask2/venv/lib/python2.7/site-packages/setuptools-28.8.0.dist-info/INSTALLER: 365c9bfeb7d89244f2ce01c1de44cb85

/Users/jacksonshawn/PythonCodes/pythonlearning/flask2/venv/lib/python2.7/site-packages/pip-9.0.1.dist-info/top_level.txt: 365c9bfeb7d89244f2ce01c1de44cb85

/Users/jacksonshawn/PythonCodes/pythonlearning/flask2/venv/lib/python2.7/site-packages/wheel-0.29.0.dist-info/INSTALLER: 365c9bfeb7d89244f2ce01c1de44cb85

通過文件路徑查看每一個文件的內容,結果如下。每一個文件的內容都是一個單詞「pip」,文件大小均為4位元組。可以看出,不同的文件名,只要內容相同,計算出來的md5值是同一個。

(圖1)

(圖2)

(圖3)

原本到這裡就可以愉快地發布這篇文章了,但是,好奇的我,腦子裡產生一個問題,「在Python3裡面計算這個文件的md5值,結果會一樣嗎?」

我打開虛擬機,在Win7裡面創建一個b.txt文件和一個b.docx文件,各自都只有一行內容,一個單詞「pip」,然後使用在Python3.6.4環境下計算著這兩個文件的md5值。結果出人意料,TXT文件的md5值是62ad1c2a46c5298f3e2c95d3babf8d0c,Word文件的md5值是a6e0e738b4ee07c39b6d87e91a1569a7,都不是預期的結果。

於是,我陷入了沉思,那些朦朦朧朧的疑惑再次縈繞腦海。「同樣的內容,Python2和Python3計算出來的md5值難道不一樣嗎?」

(圖4)

網上搜了一大圈,都找不到答案。在微信群里發帖提問,經一個網友提醒,才發現在Win7裡面新建的TXT和Word文件,並不是macOS裡面的原文件,它們不是同一個東西。macOS里的原文件大小是4位元組,而Win7裡面的TXT和Word文件分別是3位元組和11056位元組。這就是md5值不一樣的真正原因,即便它們的內容是相同的。

(圖5)

直接將macOS里原文件複製到Win7,得到INSTALLER.txt文件,再計算md5值,這次得到的結果就和預期一致了。

(圖6)

緊接著,我在macOS裡面將圖1(你選圖1、圖2、圖3任意一個都可以)里的文件,加上一個換行符,計算它的md5值,也得到一個不同於365c9bfeb7d89244f2ce01c1de44cb85的結果,很簡單,文件內容變了。這下我才真正理解廖雪峰Python教程hashlib那一節裡面那段話的含義。md5值的意義主要在於防篡改,哪怕你只改動了一丁點內容,md5值也會發生變化。

舉個例子,你寫了一篇文章,內容是一個字元串how to use python hashlib - by Michael,並附上這篇文章的摘要是2d73d4f15c0db7f5ecb321b6a65e5d6d。如果有人篡改了你的文章,並發表為how to use python hashlib - by Bob,你可以一下子指出Bob篡改了你的文章,因為根據how to use python hashlib - by Bob計算出的摘要不同於原始文章的摘要。

結論:

其實,同一個文件或字元,在任何語言、環境里計算出來的md5值都是相同的,因為全世界的MD5摘要演算法都一樣。在極特殊條件下,md5值會出現碰撞,但它太特殊我們一般遇不到,可以忽略不計。如果對同一個文件或字元,你計算出來的md5值不同,那麼肯定是這個文件或字元發生了變化,如同Win7里那個b.txt文件,內容也是「pip」,但實際上和macOS里的INSTALLER並不是同一個文件。

幾點補充:

第一,md5值哈希值是兩個概念,千萬別搞混淆。MD5是最常見的一種摘要演算法,它計算出來的結果稱為md5值,它是一個32位長度的16進位字元串。哈希值是一個整型數字,它主要用於字典查找時比較字典的key值,對於Python來說,每一個可哈希的對象都有一個哈希值,像int、str、unicode、tuple這些對象,都可以計算它們的哈希值,見圖7;像list、dict、set這些對象,則沒有哈希值,見圖8。

hash(object)

Return the hash value of the object (if it has one). Hash values are integers. They are used to quickly compare dictionary keys during a dictionary lookup. Numeric values that compare equal have the same hash value (even if they are of different types, as is the case for 1 and 1.0).

上面是Python裡面對哈希值下的定義,出處Python hash definition。Java語言裡面有一個hashCode類型,和Python裡面的哈希值是一個意思,它也是一個整型數字。

(圖7)

(圖8)

第二,dirlist函數里使用到了遞歸,從列印結果來看,這個遞歸調用,實現了類似深度優先的效果。當發現是文件時才收集文件路徑,是文件夾則調用本身,遍歷下一層,循環往複。因此,文件路徑深的會先被掃描到allfile列表裡面,文件路徑淺的後被掃描到。

第三,dirlist函數裡面判斷文件類型時又一個else分支,滿足else分支的文件路徑會被寫入shortcutfile列表。這個shortcutfile列表存入的都是「替身變數」,在macOS里叫法是「替身變數」,在Windows裡面叫法是快捷方式。我在測試中發現,對於實體文件的「替身變數」,計算出來的md5值和原文件的md5值一樣;但對於一些軟連接「替身變數」,沒法計算md5值。這一點,有待日後進一步觀察和研究。

原文鏈接:

真正弄懂md5值是什麼?

bigbigben.com圖標
推薦閱讀:

怎樣在 Mac 上查看文件的 MD5 值?
md5 編碼可以反編碼出來么?就是已經知道生成的 md5 編碼,反推源文件
有哪些值得推薦的 MD5 在線解密網站?
加盐密码保存的最通用方法是?
MD5校驗會有極低的碰撞率,那麼經過MD5之後產生的16個位元組再次進行MD5運算會不會進一步降低碰撞率?

TAG:哈希函數 | MD5 | Python |