ハッシュ値はデジタル署名,パスワードの保存,ブロックチェーンなど様々な手続きで利用されおり,ハッシュ値を使うことで改ざん検知や情報の認証などができます.ハッシュ関数に何らかのデータを与えると,元のデータがどのようなサイズであっても短い固定長のハッシュ値が得られます.ハッシュ関数は一方向性の関数で,この関数と関数から得られたハッシュ値が分かっていても元のデータは算出できません.また,異なるデータから同じハッシュ値が得られることはほとんどなく,元のデータがわずか1ビット異なるだけでも,得られるハッシュ値は大きく異なるという特徴を持ちます.
Python でハッシュ値を利用するには hashlib
というライブラリをインポートする必要があります.
import hashlib
ハッシュ値を計算するためには,ハッシュ関数にバイト列を渡す必要があります.まずは,「abc」という文字列をバイト列に変換して,SHA256 というはハッシュアルゴリズムによってハッシュ値を求めてみます.SHA256 は 256 ビットのハッシュ値を出力するので,結果は 64 桁の 16 進数となります.なお,b""
で文字列をバイト列に変換できます.
hashlib.sha256(b"abc").hexdigest()
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
「abc」を「abs」に1文字だけ変更してみます.ASCII コード表 を参照すると「c」は「0110 0011」で「s」は「0111 0011」なので,1ビット異なるだけです.しかしながら,得られたハッシュ値は大きく異なることがわかりました.
hashlib.sha256(b"abs").hexdigest()
'78da4a596a88bc5114f071ba590793bf3b37329d761230f33129983a747f414e'
MD5 は 128 ビット(16進数で32桁)のハッシュ値を生成するアルゴリズムです.この MD5 は脆弱性が明らかになっています.
hashlib.md5(b"abc").hexdigest()
'900150983cd24fb0d6963f7d28e17f72'
SHA384 は 384 ビット(16進数で96桁)のハッシュ値を生成するアルゴリズムです.
hashlib.sha384(b"abc").hexdigest()
'cb00753f45a35e8bb5a03d699ac65007272c32ab0eded1631a8b605a43ff5bed8086072ba1e7cc2358baeca134c825a7'
SHA512 は 512 ビット(16進数で128桁)のハッシュ値を生成するアルゴリズムです.
hashlib.sha512(b"abc").hexdigest()
'ddaf35a193617abacc417349ae20413112e6fa4e89a97ea20a9eeee64b55d39a2192992a274fc1a836ba3c23a3feebbd454d4423643ce80e2a9ac94fa54ca49f'
以下では SHA256 を利用しますが,これ以外のアルゴリズムも同じように利用できるはずです.
文字列からハッシュ値を求めるためにはエンコードしてから(つまり文字列をバイト列に変換してから)ハッシュ関数に渡します.上で説明したとおり,b""
を使ってエンコードするとバイト列になります.
文字列形式
type("abc")
str
バイト列に変換(エンコード)
type(b"abc")
bytes
ハッシュ値を求める
hashlib.sha256(b"abc").hexdigest()
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
文字列オブジェクトは encode( )
メソッドでエンコードできます.このとき,エンコード方式は utf-8
形式が無難です.
msg = "abc"
type(msg)
str
type(msg.encode('utf-8'))
bytes
したがって,次のような方法で文字列からハッシュ値を計算できます.
msg = "abc"
byte_msg = msg.encode('utf-8')
hashlib.sha256(byte_msg).hexdigest()
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
文字列に含まれる文字がアルファベットや数字など,ASCIIコード表に含まれる文字だけであることが明らかな場合はエンコーディングに ascii
を使っても同じ結果を得ることができます.しかしながら,これを積極的に使う理由は見当たりません.utf-8
が推奨されます.
msg = "abc"
byte_msg = msg.encode('ascii')
hashlib.sha256(byte_msg).hexdigest()
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
次は,日本語の文字列「あいう」についてハッシュ値を求めてみましょう.まず,オブジェクトを準備して,そのデータ型を確認します.
msg = "あいう"
print(type(msg))
<class 'str'>
文字列の長さは 3(文字)です.
len(msg)
3
これを utf-8
でエンコーディングし,バイト列の中身とデータ型を表示します.この結果は「あ」という文字が16進数で「E3-81-82」という 3 バイトのバイト列にエンコードされていることを意味します.
byte_msg = msg.encode('utf-8')
print(byte_msg)
print(type(byte_msg))
b'\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86' <class 'bytes'>
1 文字あたり 3 バイトで 3 文字あることから,バイト列の長さは 9 (バイト)になります.
len(byte_msg)
9
ハッシュ値を求めてみます.
hashlib.sha256(byte_msg).hexdigest()
'486da9b15cffbdea0966687981c51c0281c446681fdc22dad0b8fdca83e99f09'
もちろん,日本語文字を ASCII コードにエンコードすることはできません.
msg = "あいう"
byte_msg = msg.encode('ascii')
------------------------------------------------------- UnicodeEncodeError Traceback (most recent call last)
説明が長くなったので,ハッシュ値を求める最低限のコードだけを示します.つまり,英文からハッシュ値を取得するコードと何ら変わりがないことがわかりました.
msg = "あいう"
byte_msg = msg.encode('utf-8')
hashlib.sha256(byte_msg).hexdigest()
'486da9b15cffbdea0966687981c51c0281c446681fdc22dad0b8fdca83e99f09'
数値からハッシュ値を求める方法のひとつは,文字列に変換してからハッシュ関数に渡す方法です.ここで説明したとおり,str( )
関数で文字列に変換できれば,あとはエンコードしてからハッシュ値を求めるだけです.
a = 123
str_a = str(a)
byte_a = str_a.encode('utf-8')
hashlib.sha256(byte_a).hexdigest()
'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3'
しかしながら,浮動小数点数の場合は,次の2つで精度が異なりますが同じハッシュ値になってしまいます.この場合は ここで説明した f 文字列を使って書式を整えるなど,別の方法が必要になる可能性があります.
a = 123.0
str_a = str(a)
byte_a = str_a.encode('utf-8')
hashlib.sha256(byte_a).hexdigest()
'0589abecb964e139e06fd64a22365653e56ca8fb6d83226d4e395cac3de94b49'
a = 123.000
str_a = str(a)
byte_a = str_a.encode('utf-8')
hashlib.sha256(byte_a).hexdigest()
'0589abecb964e139e06fd64a22365653e56ca8fb6d83226d4e395cac3de94b49'
str( )
関数を使うとリストを文字列に変換することができるので,変換された文字列をさらにバイト列に変換した後にハッシュ値を算出できます.
scores = [10, 30, 25, 20]
str_scores = str(scores)
byte_scores = str_scores.encode('utf-8')
hashlib.sha256(byte_scores).hexdigest()
'a39056c853ca287fe618c0594a8711eba1f938664460afc746e3f1de2f75801d'
辞書についても str( )
で文字列に変換し,さらにバイト列に変換すればハッシュ値を求める準備ができます.
novels = {
"genji" : "どの天皇様の御代であったか、女御とか更衣とかいわれる後宮がおおぜいいた中に、最上の貴族出身ではないが深い御愛寵を得ている人があった。",
"bocchan" : "親譲の無鉄砲で小供の時から損ばかりしている。",
"meros" : "メロスは激怒した。必ず、かの邪智暴虐の王を除かなければならぬと決意した。",
"rashomon" : "ある日の暮方の事である。一人の下人が、羅生門の下で雨やみを待っていた。",
}
str_novels = str(novels)
byte_novels = str_novels.encode('utf-8')
hashlib.sha256(byte_novels).hexdigest()
'a83ebb930d4e9fb2b42dd90bb8a6b568bf61daabdad47c2f26fd06bb7583c74f'
ファイルを開いてその内容からハッシュ値を求めることも難しくありません.まずは検証のために「abc」だけが入力されたテキストファイル「abc.txt」を作成してノートブックと同じフォルダに設置します.このとき,最後に改行文字などが含まれていないことを確認してください.ファイルサイズが「3バイト」になっているはずです.
abc.txt の中身
abc
ハッシュ値を求めたいファイルをバイナリ形式 (b) の読み取りモード (r) で開き,read( )
で読み込んだ内容からハッシュ値を求めます.
f = open('abc.txt', 'rb') # ファイルをバイナリ (b) 形式の読み取りモード (r) で開く
data = f.read() # ファイルを読み込む
hash_data =hashlib.sha256(data).hexdigest()
print(hash_data)
f.close() # ファイルを閉じる
ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad
上の結果は,「abc」という文字列から取得したハッシュ値と一致することがわかります.つまり正しく動作していることが検証できました.
hashlib.sha256( b"abc").hexdigest()
'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'
上のコードはテキストファイルだけでなく画像や動画のようなバイナリファイルでも動作します.例えば MS-Excel で作成した「Excel.xlsx」のハッシュ値を求めてみます.
f = open('Excel.xlsx', 'rb') # ファイルをバイナリ (b) 形式の読み取りモード (r) で開く
data = f.read() # ファイルを読み込む
hash_data = hashlib.sha256(data).hexdigest()
print(hash_data)
f.close() # ファイルを閉じる
e30a8ee9127d59364a4e00e0b03923a202308d6e41e18f7ac2b0c278f15d2385
このような方法は,(1) ファイルの改ざんを検知する,(2) 内容が同一である重複ファイルを検出する,などにも応用できます.