如何用Python计算文件系统目录的哈希值?

32 投票
9 回答
40674 浏览
提问于 2025-04-18 14:37

我正在用这段代码来计算一个文件的哈希值:

m = hashlib.md5()
with open("calculator.pdf", 'rb') as fh:
    while True:
        data = fh.read(8192)
        if not data:
            break
        m.update(data)
    hash_value = m.hexdigest()

    print  hash_value

当我在一个名为“folder”的文件夹上试的时候,我得到了:

IOError: [Errno 13] Permission denied: folder

我该如何计算一个文件夹的哈希值呢?

9 个回答

3

我在各种论坛上看到这段代码被反复提到。

这个ActiveState的解决方案确实有效,但正如Antonio所指出的,它在不同的文件系统上可能无法保证结果一致,因为文件的顺序可能不同(你可以试试看)。一种解决办法是把

for root, dirs, files in os.walk(directory):
  for names in files:

改成

for root, dirs, files in os.walk(directory):
  for names in sorted(files): 

(是的,我这里有点懒。这只对文件名进行了排序,而没有对文件夹进行排序。不过同样的原理适用)

7

我对答案中提到的那个方法不是很喜欢。我有一个更简单的版本在用:

import hashlib
import os


def hash_directory(path):
    digest = hashlib.sha1()

    for root, dirs, files in os.walk(path):
        for names in files:
            file_path = os.path.join(root, names)

            # Hash the path and add to the digest to account for empty files/directories
            digest.update(hashlib.sha1(file_path[len(path):].encode()).digest())

            # Per @pt12lol - if the goal is uniqueness over repeatability, this is an alternative method using 'hash'
            # digest.update(str(hash(file_path[len(path):])).encode())

            if os.path.isfile(file_path):
                with open(file_path, 'rb') as f_obj:
                    while True:
                        buf = f_obj.read(1024 * 1024)
                        if not buf:
                            break
                        digest.update(buf)

    return digest.hexdigest()

我发现每当遇到像alias这样的东西时,通常会抛出异常(在os.walk()中会出现,但你不能直接打开它)。使用os.path.isfile()这个检查可以解决这些问题。

如果在我想要计算哈希值的目录中有一个实际的文件,但它无法打开,跳过这个文件继续处理并不是一个好办法。这会影响哈希值的结果。与其这样,不如干脆放弃这个哈希尝试。在这里,try语句会包裹住我调用hash_directory()函数的部分。

>>> try:
...   print(hash_directory('/tmp'))
... except:
...   print('Failed!')
... 
e2a075b113239c8a25c7e1e43f21e8f2f6762094
>>> 
23

这里有一个实现方法,它使用了pathlib.Path,而不是依赖于os.walk。这个方法在遍历目录之前会先对目录内容进行排序,因此在不同的平台上运行时结果是可以重复的。它还会根据文件和目录的名称更新哈希值,所以如果你添加了空文件或空目录,哈希值也会发生变化。

带有类型注释的版本(适用于Python 3.6及以上版本):

import hashlib
from _hashlib import HASH as Hash
from pathlib import Path
from typing import Union


def md5_update_from_file(filename: Union[str, Path], hash: Hash) -> Hash:
    assert Path(filename).is_file()
    with open(str(filename), "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash.update(chunk)
    return hash


def md5_file(filename: Union[str, Path]) -> str:
    return str(md5_update_from_file(filename, hashlib.md5()).hexdigest())


def md5_update_from_dir(directory: Union[str, Path], hash: Hash) -> Hash:
    assert Path(directory).is_dir()
    for path in sorted(Path(directory).iterdir(), key=lambda p: str(p).lower()):
        hash.update(path.name.encode())
        if path.is_file():
            hash = md5_update_from_file(path, hash)
        elif path.is_dir():
            hash = md5_update_from_dir(path, hash)
    return hash


def md5_dir(directory: Union[str, Path]) -> str:
    return str(md5_update_from_dir(directory, hashlib.md5()).hexdigest())

没有类型注释的版本:

import hashlib
from pathlib import Path


def md5_update_from_file(filename, hash):
    assert Path(filename).is_file()
    with open(str(filename), "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash.update(chunk)
    return hash


def md5_file(filename):
    return md5_update_from_file(filename, hashlib.md5()).hexdigest()


def md5_update_from_dir(directory, hash):
    assert Path(directory).is_dir()
    for path in sorted(Path(directory).iterdir(), key=lambda p: str(p).lower()):
        hash.update(path.name.encode())
        if path.is_file():
            hash = md5_update_from_file(path, hash)
        elif path.is_dir():
            hash = md5_update_from_dir(path, hash)
    return hash


def md5_dir(directory):
    return md5_update_from_dir(directory, hashlib.md5()).hexdigest()

如果你只需要对目录进行哈希,这里有一个简化版:

def md5_update_from_dir(directory, hash):
    assert Path(directory).is_dir()
    for path in sorted(Path(directory).iterdir(), key=lambda p: str(p).lower()):
        hash.update(path.name.encode())
        if path.is_file():
            with open(path, "rb") as f:
                for chunk in iter(lambda: f.read(4096), b""):
                    hash.update(chunk)
        elif path.is_dir():
            hash = md5_update_from_dir(path, hash)
    return hash


def md5_dir(directory):
    return md5_update_from_dir(directory, hashlib.md5()).hexdigest()

使用方法:md5_hash = md5_dir("/some/directory")

23

可以使用一个叫做checksumdir的Python包来计算文件夹的校验和(也就是哈希值)。这个包可以在这里找到:https://pypi.python.org/pypi/checksumdir

使用方法:

import checksumdir
hash = checksumdir.dirhash("c:\\temp")
print hash
10

这个食谱提供了一个很不错的函数,可以完成你想要的功能。我把它修改了一下,使用了MD5哈希,而不是SHA1,因为你最开始的问题是关于MD5的。

def GetHashofDirs(directory, verbose=0):
  import hashlib, os
  SHAhash = hashlib.md5()
  if not os.path.exists (directory):
    return -1

  try:
    for root, dirs, files in os.walk(directory):
      for names in files:
        if verbose == 1:
          print 'Hashing', names
        filepath = os.path.join(root,names)
        try:
          f1 = open(filepath, 'rb')
        except:
          # You can't open the file for some reason
          f1.close()
          continue

        while 1:
          # Read file in as little chunks
          buf = f1.read(4096)
          if not buf : break
          SHAhash.update(hashlib.md5(buf).hexdigest())
        f1.close()

  except:
    import traceback
    # Print the stack traceback
    traceback.print_exc()
    return -2

  return SHAhash.hexdigest()

你可以这样使用它:

print GetHashofDirs('folder_to_hash', 1)

输出的结果看起来像这样,因为它会对每个文件进行哈希处理:

...
Hashing file1.cache
Hashing text.txt
Hashing library.dll
Hashing vsfile.pdb
Hashing prog.cs
5be45c5a67810b53146eaddcae08a809

这个函数调用返回的值就是哈希值。在这个例子中,结果是5be45c5a67810b53146eaddcae08a809

撰写回答