はじめに
パスワードをハッシュ化する際に、SaltとかPepperとかっていうキーワードがありますよね。
ハッシュ化のソースコードがぱっと見た限りでは見つからなかったので、SaltとPepperを使いつつ、漏洩してしまっても安心と言えるソースコードを書いていこうと思います。
(漏洩しないことが一番ですが。。)
Saltの生成
Saltの特徴は
- ランダムな文字列であること
- 各レコード毎に固有のものであること
の2つがあります。
各レコード毎に固有のものでなければいけないが故に、多くの場合パスワードが保存されているテーブルと同じテーブルに保存されているようですね。(そういう記事を見ただけで、実際にそうなのかは筆者は知りません)
しかし、パスワードが保存されているテーブルそのものが漏洩してしまった場合、Saltはあまり意味をなくしてしまいます。
ということで、Saltを保存するのはやめて、使用する都度生成しましょう!
毎回同じSaltを生成できれば、保存しなくても問題ありませんね。
擬似乱数を生成する際にはseedを指定できる場合がほとんどです。seedを指定することで、何度擬似乱数を生成しても同じ結果が得られます。
この擬似乱数の特性を利用してSaltを生成します。
seedには、ユーザID等の連番、もしくは各ユーザ毎に固有の数値を使用しましょう。
import random
import string
salt_type = string.ascii_letters + string.digits + '!?'
salt_length = 12
def hash(id, password):
random.seed(id)
salt = ''.join(random.choices(salt_type, k=salt_length))
Saltの種類はとりあえずアルファベットの大文字・小文字、数字と記号の「!?」だけ入れていますが、実際に使用する際は適宜追加してください。
Pepperを決める
Pepperには次のような特徴があります。
- データベースとは関係のないところに大事に保存されている
- 同じ文字列を複数ユーザで使い回している
- パスワードについて理解している人間が決められる
パスワード内にアルファベットしか入力しない人がいた場合、レインボーテーブルで平文が簡単に見つかってしまう場合がありますが、Pepperで記号や数字を混ぜることでレインボーテーブルで見つかりにくくすることができます。
と言っても、現在はWebアプリ側で複雑なパスワードを入力するように求めることがほとんどですが。
今回は次のようなPepperを使用しようと思います。
pepper = 'Qiit4!'
ここではソースコード内に書いてしまっていますが、実際は安全なところから取得するようにしてくださいね。
ハッシュ化
今回のハッシュ関数はSHA3-224を使用します。
ハッシュの出力長は長ければ長いほど衝突する確率は下がりますが、今回はサンプルソースコードということで、短めのものを使用します。
また、通常ハッシュ値を不正に入手した人は、ハッシュ値が256桁だった場合、使用したハッシュ関数はSHA3-256やSHA-256、もしくは可変長のものと推測します。
そこで、ハッシュ値のようなランダムな16進数を追加してSHA3-256を使用していると見せかけましょう。
もし、全パターンの文字列をハッシュ化したレインボーテーブルを持っていたとしても、ハッシュ関数が別のものを使用していたならば関係ありません。
今回はSHA3を使用しておりますが、実際に使用する際はできるだけ最新のものを使用するようにしてください。
suffix_length
の値だけ気を付けて頂ければ大丈夫です。
import random
import hashlib
import string
salt_type = string.ascii_letters + string.digits + '!?'
salt_length = 12
pepper = 'Qiit4!'
hexes = '0123456789abcdef'
suffix_length = (256 - 224) // 4
def hash(id, password):
random.seed(id)
salt = ''.join(random.choices(salt_type, k=salt_length))
pw = salt + password + pepper
hash_224 = hashlib.sha3_224(pw.encode()).hexdigest()
hash_suffix = ''.join(random.choices(hexes, k=suffix_length))
hash_256 = hash_224 + hash_suffix
return hash_256
print(hash(100, 'abcdABCD'))
magic_numberの導入
ここまでやってまだ安心し足りない、という方はお塩に魔法をかけましょう。
こうすると、あら不思議、さらに推測しにくくなるような気がします。
...気がするだけ?
使い方はsaltを生成する元の数字に加算します。
これにより、完全にDBには存在しない情報からsaltを生成することになります。
import random
import hashlib
import string
salt_type = string.ascii_letters + string.digits + '!?'
salt_length = 12
pepper = 'Qiit4!'
hexes = '0123456789abcdef'
suffix_length = (256 - 224) // 4
magic_number = 10
def hash(id, password):
random.seed(id + magic_number)
salt = ''.join(random.choices(salt_type, k=salt_length))
pw = salt + password + pepper
hash_224 = hashlib.sha3_224(pw.encode()).hexdigest()
hash_suffix = ''.join(random.choices(hexes, k=suffix_length))
hash_256 = hash_224 + hash_suffix
return hash_256
print(hash(100, 'abcdABCD'))
もしこのmagic_numberを使う際は、これもPepperと同じく安全なところに保管してくださいね。
最後に
ここまで記事を読んで頂きありがとうございました。
この記事は色々な事情でbcrypt等のハッシュアルゴリズムを使用できない方向けに書いております。
サードパーティ製のものをインストールできない、使用している言語が古く最近のライブラリを使用できない等、、、
ハッシュアルゴリズムを使用できる場合はそちらを使いましょう!
そうした方が後任のエンジニアは楽です!笑