Python比较本地和远程文件的MD5哈希值

4

我正在尝试比较本地和远程文件的MD5哈希值(将相同的文件复制/粘贴到我的wamp“www”目录中),但我不明白为什么“校验和”不对应...

以下是校验和代码:

#-*- coding: utf-8 -*-

import hashlib
import requests

def md5Checksum(filePath,url):
    if url==None:
        with open(filePath, 'rb') as fh:
            m = hashlib.md5()
            while True:
                data = fh.read(8192)
                if not data:
                    break
                m.update(data)
            return m.hexdigest()
    else:
        r = requests.get(url, stream=True)
        m = hashlib.md5()
        for line in r.iter_lines():
            m.update(line)
        return m.hexdigest()

print "checksum_local :",md5Checksum("projectg715gb.pak",None)
print "checksum_remote :",md5Checksum(None,"http://testpangya.ddns.net/projectg715gb.pak")

我很惊讶地得到了这个输出:

checksum_local : 9d33806fdebcb91c3d7bfee7cfbe4ad7
checksum_remote : a13aaeb99eb020a0bc8247685c274e7d

"projectg715gb.pak"的大小为14.7Mb

但如果我尝试一个文本文件(大小为1Kb):

print "checksum_local :",md5Checksum("toto.txt",None)
print "checksum_remote :",md5Checksum(None,"http://testpangya.ddns.net/toto.txt")

然后它就可以工作了,我得到了以下输出:

checksum_local : f71dbe52628a3f83a77ab494817525c6
checksum_remote : f71dbe52628a3f83a77ab494817525c6

我还不太了解比较MD5哈希值,所以请大家多多包涵^^' 我可能会犯一些错误,我不明白为什么它不能处理大文件,如果有人能给我一些提示,那就太好了!

无论如何感谢你的阅读和帮助!


3
在一个代码片段中,每次读取8192个字节,而在另一个代码片段中,每次读取整行。每行的长度可能并不是恰好8192个字节。只需将fh.read(8192)更新为fh.readline()即可。请注意保持翻译后的内容与原文意思一致,并尽量使其通俗易懂。 - Nick Chapman
@NickChapman 我尝试过了,所以如果我打印数据和行,它看起来更相似,但校验和仍然不同:'( - Garbez François
1
.iter_lines() 在 URL 版本中会丢弃数据中的任何换行符。使用 .iter_content() 按块读取数据,就像文件版本一样。 - jasonharper
谢谢!!! @jasonharper 运行良好^^ - Garbez François
3个回答

6
因为有帮手的帮助,这是最终可行的代码:
#-*- coding: utf-8 -*-

import hashlib
import requests

def md5Checksum(filePath,url):
    m = hashlib.md5()
    if url==None:
        with open(filePath, 'rb') as fh:
            m = hashlib.md5()
            while True:
                data = fh.read(8192)
                if not data:
                    break
                m.update(data)
            return m.hexdigest()
    else:
        r = requests.get(url)
        for data in r.iter_content(8192):
             m.update(data)
        return m.hexdigest()

print "checksum_local :",md5Checksum("projectg715gb.pak",None)
print "checksum_remote :",md5Checksum(None,"http://testpangya.ddns.net/projectg715gb.pak")

2

好的,看起来我找到了一个解决方案,我会在这里发布它:)

首先,您需要编辑位于服务器上您的文件所在目录的.htaccess文件。

.htaccess文件内容:

ContentDigest On

现在您已经设置好了,服务器应该在HTTP头中发送Content-MD5数据。
这将导致类似于以下内容的结果:
'Content-MD5': '7dVTxeHRktvI0Wh/7/4ZOQ=='

现在让我们看一下Python部分,所以我修改了我的代码,使其能够比较这个HTTP头数据和本地MD5校验和。

#-*- coding: utf-8 -*-

import hashlib
import requests
import base64

def md5Checksum(filePath,url):
    m = hashlib.md5()
    if url==None:
        with open(filePath, u'rb') as fh:
            m = hashlib.md5()
            while True:
                data = fh.read(8192)
                if not data:
                    break
                m.update(data)
            #Get BASE 64 Local File md5
            return base64.b64encode(m.digest()).decode('ascii')#Encode MD5 digest to BASE 64
            
    else:
        #Get BASE 64 Remote File md5
        r = requests.head(url) #You read HTTP Header here
        return r.headers['Content-MD5'] #Take only Content-MD5 string

def compare():
    local = md5Checksum("projectg502th.pak.zip",None)
    remote = md5Checksum(None,"http://127.0.0.1/md5/projectg502th.pak.zip")

    if local == remote :
        print("The soft don't download the file")
    else:
        print("The soft download the file")

print ("checksum_local :",md5Checksum("projectg_ziinf.pak.zip",None))
print ("checksum_remote : ",md5Checksum(None,"http://127.0.0.1/md5/projectg_ziinf.pak.zip"))

compare()

输出:

checksum_local : 7dVTxeHRktvI0Wh/7/4ZOQ==
checksum_remote : 7dVTxeHRktvI0Wh/7/4ZOQ==
The soft don't download the file

我希望你能帮上忙;)

我在WAMP上对Apache 2.4.4进行了测试。 - Garbez François
1
这真是太酷了。我不知道你可以通过htaccess做到这一点。我正在使用在服务器上托管的json文件,以获取远程文件的已知好校验和,而无需下载它进行比较。如果您在应用程序中解析它,您可能也会喜欢这种方法。如果您感兴趣,可以查看下面的链接,这可能是您采取的另一种方法来发布和维护已知的哈希校验和。我有很多文件,所以最终将它们合并成一个json文件,在应用程序内提取。 https://wizardassistant.com/wizardassistant_app_current_release_url.json - Mike R

1
感谢您发布您的解决方案 https://stackoverflow.com/users/7495742/framb-axa,对我的问题非常有帮助。我稍微修改了md5部分和Python3的打印语句,并将它们交换为sha256以满足我的需求,用于下载/检查我构建的应用程序的本地和远程sqlite数据库。在此留下代码作为参考,供其他可能偶然发现这篇文章的人使用。
import hashlib
import requests


# current release version url
current_release_url = 'https://somedomain.here/current_release.txt'
current_release_notes_url = 'https://somedomain.here/current_release_notes.txt'

# current database release  version url
current_db_release_url = 'https://somedomain.here/current_db_release.txt'
current_db_release_notes_url = 'https://somedomain.here/current_db_release_notes.txt'
current_db_release_notes_hash_url = 'https://somedomain.here/current_db_release_hash.txt'
current_db_release = ''
wizard_db_version = ''

# Default commands DB url
wizard_cmd_db_url = 'https://somedomain.here/sqlite.db'

wizard_cmd_db = 'some/path'

checksum_local = ''
checksum_remote = ''
checksum_remote_hash = ''
checksum_status = ''


def download_cmd_db():
    try:
        print('Downloading database update version: ' + str(current_db_release))
        url = wizard_cmd_db_url
        r = requests.get(url)
        with open(wizard_cmd_db, 'wb') as f:
            f.write(r.content)

        # Retrieve HTTP meta-data
        print(r.status_code)
        # print(r.headers['content-type'])
        # print(r.encoding)
        settings.setValue('wizard_db_version', current_db_release)
        print('Database downloaded to:' + str(wizard_cmd_db))
    except:
        print('Commands Database download failed.... ;( ')


def sha256_checksum(filepath, url):
    m = hashlib.sha256()
    if url is None:
        with open(filepath, 'rb') as fh:
            m = hashlib.sha256()
            while True:
                data = fh.read(8192)
                if not data:
                    break
                m.update(data)
            return m.hexdigest()
    else:
        r = requests.get(url)
        for data in r.iter_content(8192):
            m.update(data)
        return m.hexdigest()


def wizard_db_hash_check():
    global checksum_local, checksum_remote, checksum_status
    try:
        checksum_local = sha256_checksum(wizard_cmd_db, None)
        checksum_remote = sha256_checksum(None, wizard_cmd_db_url)
        print("checksum_local : " + checksum_local)
        print("checksum_remote: " + checksum_remote)
        print("checksum_remote_hash: " + checksum_remote_hash)

        if checksum_local == checksum_remote_hash:
            print('Hash Check passed')
            checksum_status = True
        else:
            print('Hash Check Failed')
            checksum_status = False
    except:
        print('Could not perform wizard_db_hash_check')


# Sanity check for missing database file
file = pathlib.Path(wizard_cmd_db)
if file.exists():
    print("DB File exists: " + wizard_cmd_db)
    wizard_db_hash_check()
else:
    print("DB File does NOT exist: " + wizard_cmd_db)
    download_cmd_db()
    wizard_db_hash_check()

# Check hash

# # Logic to decide when to download DB here
try:
    if int(current_db_release) > int(wizard_db_version):
        print('Database update available: ' + str(current_db_release))
        download_cmd_db()
        wizard_db_hash_check()
except:
    print('Unable to check wizard_db_release')

if checksum_local != checksum_remote:
    download_cmd_db()
    wizard_db_hash_check()

# Logic to fallback to default packaged DB if no internet to download and compare hash
if checksum_status is True:
    target_db = str(wizard_cmd_db)
else:
    print('All hash checks and attempts to update commands DB have failed. Switching to bundled DB')
    target_db = os.path.join(os.path.abspath(os.path.dirname(sys.argv[0])), "sqlite.db")

print('Sanity Checks completed')

我现在唯一的问题是,当我尝试在远程服务器上使用此解决方案时,获取远程文件的MD5校验和需要很长时间.... :((( - Garbez François
如果远程文件很大,它必须完全下载以检查哈希值,所以是的。如果这是您控制的服务器上的远程文件,您也可以轻松通过ssh获取总和。我使用以下命令在ssh上获取哈希值,因为哈希是在服务器上完成的。这不是确认本地文件哈希的理想方法,但可以用来查看应该是什么。 ssh username@someserver "sha256sum /home/some/path/to/file" - Mike R
我找到了另一种方法,只需查看下一个答案 :) - Garbez François

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接