Pythonで巨大ファイルのハッシュ値（チェックサム）を求める方法

## 1, はじめに

今回はPythonでデータサイズの大きなファイルのハッシュ値（チェックサム）を求める方法について説明したいと思います。
ハッシュ値を求めるには対象データのバイト配列が必要ですが、メモリに格納できないような巨大なファイルの場合はどうするのか疑問に思ったので調べてみました。

## 2. ソースコード

```python:hashApp.py
# -*- coding: utf-8 -*-
import hashlib
import argparse

# main
if __name__ == "__main__":

    # ★ポイント1
    parser = argparse.ArgumentParser(description='hashApp')
    parser.add_argument('input', help='Input file path')
    parser.add_argument('--alg', help='Input algorithm (md5, sha1, sha224, sha256, sha384, sha512)', default = 'sha1')
    args = parser.parse_args()
    
    filePath = args.input
    alg = args.alg

    # ★ポイント2
    if 'md5' == alg:
        hash = hashlib.md5()
    elif 'sha224' == alg:
        hash = hashlib.sha224()
    elif 'sha256' == alg:
        hash = hashlib.sha256()
    elif 'sha384' == alg:
        hash = hashlib.sha384()
    elif 'sha512' == alg:
        hash = hashlib.sha512()
    else:
        hash = hashlib.sha1()

    # ★ポイント3
    with open(filePath, 'rb') as f:
        while True:
            chunk = f.read(2048 * hash.block_size)
            if len(chunk) == 0:
                break

            hash.update(chunk)

    # ★ポイント4
    digest = hash.hexdigest()
    print("{0}({1}) : {2}".format(filePath, alg, digest))
```

**★ポイント1**

コマンドラインアプリとして実装するので引数の処理に[argparse.ArgumentParser](http://docs.python.jp/2/library/argparse.html#module-argparse)を利用することにしました。
デフォルト（アルゴリズムの指定なし）の場合、`sha1`でハッシュ値を算出する仕様とします。

* オプション無し引数　：　ハッシュ値を求める対象のファイルパス
* --algオプション　：　ハッシュの算出に利用するアルゴリズム

**（参考）**
利用可能なアルゴリズムは以下のコードで確認することができます。

```python:利用可能なアルゴリズムを表示するコード
print(hashlib.algorithms)
```

```console:出力例
('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
```

**★ポイント2**

引数で指定したアルゴリズムを利用してハッシュオブジェクトを生成します。
アルゴリズムに対応したコンストラクタが用意されているのでこれを利用します。
詳細については[公式ガイドライン](http://docs.python.jp/2/library/hashlib.html)を参照ください。

* hashlib.md5()
* hashlib.sha224()
* hashlib.sha256()
* hashlib.sha384()
* hashlib.sha512()
* hashlib.sha1()　：　サンプルではこれをデフォルトとします。

**★ポイント3**

対象ファイルをバッファリングしつつ読み取り、`update()`関数でハッシュオブジェクトを更新します。
データサイズが大きなファイルでも、この方法でリソース消費を抑えつつ、ハッシュ値を求めることができます。

> hash.update(arg)(原文)
> 文字列 arg でハッシュオブジェクトを更新します。繰り返し呼び出すことは引数全ての連結で一回呼び出すことと等価です。例えば m.update(a); m.update(b) は m.update(a+b) と等価です。

**★ポイント4**

最後に`hexdigest()`関数でハッシュ値を算出します。

> hash.digest()(原文)
> これまでに update() メソッドに渡した文字列のダイジェストを返します。これは digest_size バイトの文字列であり、非ASCII文字やnull バイトを含むこともあります。

> hash.hexdigest()(原文)
> digest() と似ていますが、倍の長さの、16進形式文字列を返します。これは、電子メールなどの非バイナリ環境で値を交換する場合に便利です。

## 3. テスト

ハッシュ値が分かるファイルがないかなと探したところ、windowsパッチがファイル名にハッシュ値を含んでいたのでこれを利用して確認してみます。
今回のファイルが欲しい方は[windows update catalog](https://www.catalog.update.microsoft.com/Search.aspx?q=4055532)からダウンロードできます笑。
なお、windowsパッチのハッシュ値は`sha1`となっています。

```console:windowsパッチのハッシュ値を確認してみる
# C:\py>python hashApp.py C:\tmp\ndp47-kb4055002-x64_6ede64fcd4922eaf426438e69ac6f8e28e185ea4.exe
# C:\tmp\ndp47-kb4055002-x64_6ede64fcd4922eaf426438e69ac6f8e28e185ea4.exe(sha1) : 6ede64fcd4922eaf426438e69ac6f8e28e185ea4
```

ファイル名のハッシュ値の文字列と、今回求めたハッシュ値の値が同じになっています。
これで正常にダウンロードできたことが確認できますね。

## 4. さいごに

今回はPythonでファイルサイズの大きなファイルのハッシュ値（チェックサム）を求める方法について説明しました。
今回の方法は以前の記事「[Pythonでバイナリファイルを指定サイズに分割、結合する](https://qiita.com/5zm/items/49118188d76e61ca5113)」で復元したファイルが正しいか確認するのに使えそうです。