pythonで暗号演算 ~SHAでハッシュ生成~
今回はハッシュ関数の中でも、TLS通信で主要な役割を持っているSHAについて勉強していきます。
また、pythonのpycryptodomeライブラリを使用して、ハッシュ処理を動かしてみます。
ハッシュ関数とは
概要
ハッシュ関数とは、入力データに対応するランダムな値(ハッシュ値)を生成する関数です。
入力データが同じであれば、生成されるハッシュ値も同じ値となります。
暗号化の場合、生成したデータに対して復号化を行うことで、元の値を取得できます。
一方でハッシュ関数で生成したハッシュ値から元のデータを割り出すのは、技術的に非常に困難とされています。
このことから、ハッシュ関数は不可逆性を持っているといいます。
用途
主にデータの改ざんを検知する目的で使用されます。
ハッシュ関数の主な用途は以下の通りです。
用途 | 説明 |
---|---|
電子署名 | 対象データのハッシュ値を保持しとくことで、メッセージが改ざんされていないことの保証となる。 |
電子メール | メール本文とハッシュ値を別々に送信することで、受信者は通信経路上の改善検知が出来る。 |
ファイル配布 | DLデータとハッシュ値を比較することで、データの改ざん検知が可能。 加えてDL時のファイル破損の検知が可能。 |
TLSにおいてはデジタル署名でハッシュ値が使用されています。
詳細は以下過去記事を参照してください。
SHAの種類
SHA(Secure Hash Algorithm)は、NISTによって標準のハッシュ関数に指定されているアルゴリズムの総称です。
SHAには3つのバージョンがあり、以下はその詳細になります。
(正確にはSHA-0もあるのですが、SHA-1に含められています)
ハッシュ関数 | サイズ(bit) | 補足 |
---|---|---|
SHA-1 | 160 | SHA、SHA1等と表記されるが、ともに同じハッシュ関数。 いくつかの脆弱性が報告されており、非推奨。 詳細についてはRFC6234参照。 |
SHA-2 | 224、256、384、512 | 使用するbit数に応じてSHA224、SHA256、SHA384、SHA512と表記される。 現状脆弱性のないSHA-2が主流として使用されている。 詳細についてはRFC6234参照。 |
SHA-3 | 224、256、384、512 | 使用するbit数に応じてSHA3-224、SHA3-256、SHA3-384、SHA3-512と表記される SHA-3の方がSHA-2よりもセキュリティ強度は高いが、SHA-2が主流。 詳細についてはFIPS 202参照。 |
SHA-1,2,3は使用時の呼称に統一感がない(SHA1、SHA256等)ため、初めは戸惑いました。
SHA以外にもMD5というハッシュ関数がTLSで使われていたのですが、こちらはSHA-1同様既知の脆弱性により使用は非推奨となっております。
動作確認
pycryptodomeライブラリの下記参考に実装を行いました。
ソース
from cryptography.hazmat.primitives import hashes
import time
message = b"00112233445566778899"*100000
def main():
start = time.time()*1000000
# SHA-1
start = time.time()*1000000
digest = hashes.Hash(hashes.SHA1())
digest.update(message)
print( "SHA1 " , int(time.time()*1000000-start), 'μSec' , digest.finalize())
# SHA224
start = time.time()*1000000
digest = hashes.Hash(hashes.SHA224())
digest.update(message)
print( "SHA224 " , int(time.time()*1000000-start), 'μSec' , digest.finalize())
# SHA256
start = time.time()*1000000
digest = hashes.Hash(hashes.SHA256())
digest.update(message)
print( "SHA256 " , int(time.time()*1000000-start), 'μSec' , digest.finalize())
# SHA384
start = time.time()*1000000
digest = hashes.Hash(hashes.SHA384())
digest.update(message)
print( "SHA384 " , int(time.time()*1000000-start), 'μSec' , digest.finalize())
# SHA3-224
start = time.time()*1000000
digest = hashes.Hash(hashes.SHA3_224())
digest.update(message)
print( "SHA3-224" , int(time.time()*1000000-start), 'μSec' , digest.finalize())
# SHA3-256
start = time.time()*1000000
digest = hashes.Hash(hashes.SHA3_256())
digest.update(message)
print( "SHA3-256" , int(time.time()*1000000-start), 'μSec' , digest.finalize())
# SHA3-384
start = time.time()*1000000
digest = hashes.Hash(hashes.SHA3_384())
digest.update(message)
print( "SHA3-384" , int(time.time()*1000000-start), 'μSec' , digest.finalize())
if __name__ == '__main__':
main()
出力結果
SHA1の処理時間がSHA2、3よりはるかに大きく演算速度の面においても劣っていることが分かります。
SHA1 52858 μSec b' 2lO\xa1\x80\x90\xc2\xd9\x7f\xe8\x87N\x82\x9e\x94,s\xebO'
SHA224 3022 μSec b'\xbd\xa1Bj\x15\x0cJ\xd5\xf2\x93\x95.\x02]\xe2\xe6\x9b\x8e:kV\xf1I/\x81O\xb3\xa9'
SHA256 2992 μSec b'\xae\xf2*v\x85\xfc\x95\xfeWp\xe8\x1d\xe4:\x10\x15\xda^W\x92\x19#\x10\x80\x969kM\x1c\xf1\xda\x9b'
SHA384 1994 μSec b'{\x927\xf3\xa4\xa2)w\x03\xe2\xd4\re\xb8OUF\x05\xf0(\x15\xbd?m\xbcVF\xee\x13\xd3\xed\x19D6\x01\xae\x16\r\x9c\xdb\x8d\x91}\x0f\x01$@\x95'
SHA3-224 4986 μSec b"\x95\x10\x91';K\x0b\xb2D\xf4zk\xe3\xc7\x1c5\xe5\xdb\xb8;\xed^\xbb);\x84\xe6\xcd"
SHA3-256 4986 μSec b'I\xbcr4\x06\x0b\x1f%t\xe8$\x8a\xbf\xd8\xb7\xed\xd8\x06\xfd\x0c\x0b,\xf7\xa4\xbb\x84\xcb\x19\xf1#9\x1e'
SHA3-384 5985 μSec b'\x9bML\xe6\xa1^\x1d\x19\xda,\x0c\xf4\xdb,\xac\xcd\xb6\xf2\xf3\xd2Lb\x8fuT\x90\x84\xc0\xac\xfb\x8el\n\xca\xcb\x96AE\x9cF \xc4UKammO'