真正弄懂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
):
“
%s
is a hidden folder”
%
filepath
else
:
“
%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值。這一點,有待日後進一步觀察和研究。
原文連結: