Pythonでハッシュ化を試してみる

# 事前知識

## 暗号化とハッシュ化の違い
データをもとに戻せるか戻せないかの違い。
暗号化はデータを元に戻せるが、ハッシュ化は元に戻せない。

## ハッシュ化
同一データから変換されたハッシュ値は、常に同一の値になる。
パスワード一致確認や、データの改ざん検知に使われる。
SHA-2(SHA-256)がよく使われている。
よく使われてる気がするんだけどMD5は脆弱性があるので使わない方がいいみたい。
今回メインで説明するのはこっち。

## 暗号化
データを第三者に盗み見られても簡単には読めない状態にしておくこと。
暗号化にも復号化にも同じキーを使う共通鍵暗号方式や、暗号化と復号化で異なるキーを使う公開鍵暗号方式がある。
共通鍵暗号方式と公開鍵暗号方式を組み合わせたハイブリッド方式という手法もあり、ウェブページのHTTPS化（SSL/TLS）で用いられてる。

# ハッシュ化させてみる
hashlibというモジュールを使って暗号化します。

```python:test.py
import hashlib

# 平文
message = 'Python'

# SHA-256に暗号化
hash_sha256 = hashlib.sha256(message.encode())
print(f'ダイジェスト値(bytes): {hash_sha256.digest()}')
print(f'16進数文字列(str): {hash_sha256.hexdigest()}')
```
↓実行結果
```
# python test.py 
ダイジェスト値(bytes): b"\x18\x88_'\xb5\xaf\x90\x12\xdf\x19\xe4\x96F\x0f\x92\x94\xd5\xabv\x12\x88$\xc6\xf9\x93xp\x04\xf6\xd9\xa7\xdb"
16進数文字列(str): 18885f27b5af9012df19e496460f9294d5ab76128824c6f993787004f6d9a7db
```

他にも色々なアルゴリズムで暗号化できます。
```python:test.py
import hashlib

# 平文
message = 'Python'

# MD5に暗号化したオブジェクトを16進数文字列に変換
hash_md5 = hashlib.md5(message.encode()).hexdigest()
print(f'MD5: {hash_md5}')

# SHA-256に暗号化したオブジェクトを16進数文字列に変換
hash_sha256 = hashlib.sha256(message.encode()).hexdigest()
print(f'SHA256: {hash_sha256}')

# SHA-512に暗号化したオブジェクトを16進数文字列に変換
hash_sha512 = hashlib.sha512(message.encode()).hexdigest()
print(f'SHA512: {hash_sha512}')
```
↓実行結果
```
# python test.py 
MD5_obj: <md5 _hashlib.HASH object @ 0x7f216f66c970>
MD5: a7f5f35426b927411fc9231b56382173
SHA256: 18885f27b5af9012df19e496460f9294d5ab76128824c6f993787004f6d9a7db
SHA512: fd9d4d5b7a8a8fae6b1bc099b799110f7e4338606e2610f5d9506a4346e0c3bfbc525f4eed1e05aa8c6f46b8efff526ec48b500928a1b341ade5a7855f533932
```
`message.encode()`は、`message.encode('utf-8')`のように文字コード指定可能。

## 対応しているハッシュ関数は以下で確認可能
- `hashlib.algorithms_guaranteed`: Python以外でも利用可能なアルゴリズム
- `hashlib.algorithms_available`: Pythonで利用可能なアルゴリズム
```python:test.py
import hashlib
print(hashlib.algorithms_guaranteed)
```
↓実行結果
```
# python test.py 
{'blake2b', 'blake2s', 'shake_128', 'sha256', 'sha3_256', 'shake_256', 'sha512', 'sha3_224', 'sha3_512', 'sha384', 'md5', 'sha3_384', 'sha1', 'sha224'}
```
```python:test.py
import hashlib
print(hashlib.algorithms_available)
```
↓実行結果
```
# python test.py 
{'md5', 'sha512', 'ripemd160', 'shake_256', 'blake2s', 'sha3_512', 'sha1', 'md4', 'blake2b', 'sha512_256', 'sha256', 'sha3_256', 'shake_128', 'whirlpool', 'sha224', 'md5-sha1', 'sha384', 'sha512_224', 'sha3_384', 'sm3', 'sha3_224'}
```

# より安全性を高める
平文に任意の文字列を加えた文字列でハッシュ化させる**salt**と呼ばれる手法と、任意の回数ハッシュ化を繰り返す**stretching**と呼ばれる手法を組み合わせて、より強固なハッシュ化にします。
```python:test.py
import hashlib

# 平文
message = 'Python'

# saltを生成
salt = "Solty"

# 平文とsaltを結合
salt_message = message + salt
print(f'saltを結合した文字列: {salt_message}')

# SHA-256に暗号化
sha_256 = hashlib.sha256(salt_message.encode()).hexdigest()
print(f'saltを結合したSHA-256: {sha_256}')

# stretching(繰り返しハッシュ化)してみる
for _ in range(10000):
    sha_256 = hashlib.sha256(sha_256.encode()).hexdigest()
print(f'stretchingしたSHA-256: {sha_256}')
```
↓実行結果
```
# python test.py 
saltを結合した文字列: PythonSolty
saltを結合したSHA-256: 0eb04e4ba0f0655f7c328900d211000b4a4073edccf56a7e5e796dd93d448b35
stretchingしたSHA-256: 0fa535bfbdde28c81ceb865da793dccb706386dc0d8d51ca7b2b949f18093c19
```
## pbkdf2_hmacを使ったハッシュ化
HMACという疑似乱数関数を使っている。よく分からん。
```python:test.py
import hashlib

# 平文
message = 'Python'

# saltを生成
salt = 'Solty'

# saltとstretchingをまとめて記述
sha_256 = hashlib.pbkdf2_hmac('sha256', message.encode(), salt.encode(), 10000).hex()
print(f'stretchingしたSHA-256: {sha_256}')
```
↓実行結果
```
# python test.py 
stretchingしたSHA-256: 406c0435352efc32aff93926fdf27fe33bdb022a39e78e3b5b5de5233b78ae9a
```