Python の passlib でハッシュ値を指定のソルトで計算する
概要
/etc/shadow に書かれるハッシュ値を自動生成したくなったので、調べてみた。 Ansible と組み合わせたいので、Python の passlib を使う。
他ブログでも方法は紹介されているが、 passlib で非推奨になっているメソッドが使われていたので、 公式ドキュメントみながら確かめてみた。
例えば、2.0になった段階で encrypt()
は廃止されて、hash()
のみになるようす。 現時点では passlib は 1.7 だが、1.7 では両方とも使用可能。
なお、ハッシュ値の形式は次の様になっているらしい。
${{ algorithm }}${{ option }}${{ salt }}${{ hash }}
各種OSの /etc/shadow で使えるハッシュアルゴリズムの一覧
各種OSで使えるアルゴリズムは下記から得られる。
import passlib.hosts
print("Linux: %s" % ", ".join(passlib.hosts.linux_context.schemes()))
print("FreeBSD: %s" % ", ".join(passlib.hosts.freebsd_context.schemes()))
print("OpenBSD: %s" % ", ".join(passlib.hosts.openbsd_context.schemes()))
↑の出力は↓。
Linux: sha512_crypt, sha256_crypt, md5_crypt, des_crypt, unix_disabled
FreeBSD: bcrypt, md5_crypt, bsd_nthash, des_crypt, unix_disabled
OpenBSD: bcrypt, md5_crypt, bsdi_crypt, des_crypt, unix_disabled
FreeBSD は 9.1 から sha512 に対応しているらしい。 細かく設定したい場合は自分で CryptContext を作る。
ソルトの長さを確認しておく
- http://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha512_crypt.html
- http://passlib.readthedocs.io/en/stable/lib/passlib.hash.sha256_crypt.html
- http://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt.html
アルゴリズムによって使えるソルトの長さが異なるので、上記で確認しておく。
- sha512: [./0-9A-Za-z] 0-16文字
- sha256: [./0-9A-Za-z] 0-16文字
- bcrypt: [./0-9A-Za-z] 22文字
平文パスワードからハッシュを計算する (ソルトの指定なし)
ソルトの指定をしないでハッシュを計算する。 この場合は自動でソルトが生成されていることがわかる。
import passlib.hash
secret = "secretsecret"
hashed = passlib.hash.sha512_crypt.hash(secret)
print("sha512('%s') -> '%s'" % (secret, hashed))
print("verify: %s" % passlib.hash.sha512_crypt.verify(secret, hashed))
↑の出力は↓。
sha512('secretsecret') -> '$6$rounds=656000$fcD6caqZ.HTe7aO0$0v1Xsq6tFVQVGnNX4M9A9g/UrvhJYeK53JmFl0xXmqAH5n2Mqt8XbqzX9E5VZrxXltek4CsM1ERgpIfxJRQ/l0'
verify: True
平文パスワードからハッシュを計算する (ソルトの指定あり)
ソルトを指定してハッシュを計算する。
import passlib.hash
secret = "secretsecret"
salt = "saltsalt"
hashed = passlib.hash.sha512_crypt.hash(secret, salt=salt)
print("sha512('%s') -> '%s'" % (secret, hashed))
print("verify: %s" % passlib.hash.sha512_crypt.verify(secret, hashed))
↑の出力は↓。
sha512('secretsecret') -> '$6$rounds=656000$saltsalt$Toz2h6NEmqzQZDpBJHIq3Bp1mExBVFK7rSGitHQ9F90ji1pHLC/yEAi5fbGkPpIhpMoBkY9icINEdTceZe6Ur.'
verify: True
(おまけ)ハッシュ値からアルゴリズムを特定し、内容をパースする
対象とするアルゴリズムを指定して、そのハッシュ内容をパースする場合は↓のようにすればよい。
import passlib.context
ctx = passlib.context.CryptContext(schemes=["sha256_crypt", "sha512_crypt"])
hashed = "$6$rounds=656000$saltsalt$Toz2h6NEmqzQZDpBJHIq3Bp1mExBVFK7rSGitHQ9F90ji1pHLC/yEAi5fbGkPpIhpMoBkY9icINEdTceZe6Ur."
scheme = ctx.identify(hashed)
if scheme:
for (k, v) in ctx.handler(scheme).parsehash(hashed).items():
print("%s: %s" % (k, v))
else:
print("No Match Scheme.")
↑の出力は↓。
checksum: Toz2h6NEmqzQZDpBJHIq3Bp1mExBVFK7rSGitHQ9F90ji1pHLC/yEAi5fbGkPpIhpMoBkY9icINEdTceZe6Ur.
salt: saltsalt
rounds: 656000
備考
上記を見ると rounds=656000 となっているためラウンド数は656000回だが、 glibc の実装のデフォルトは5000回らしい。
(おまけ)mkpasswd でハッシュ値を計算する
Ubuntu や Debian では whois のパッケージに mkpasswd コマンドが含まれている。
↑でインストールすれば、↓のように mkpasswd が使える。
$ mkpasswd -h
Usage: mkpasswd [OPTIONS]... [PASSWORD [SALT]]
Crypts the PASSWORD using crypt(3).
-m, --method=TYPE select method TYPE
-5 like --method=md5
-S, --salt=SALT use the specified SALT
-R, --rounds=NUMBER use the specified NUMBER of rounds
-P, --password-fd=NUM read the password from file descriptor NUM
instead of /dev/tty
-s, --stdin like --password-fd=0
-h, --help display this help and exit
-V, --version output version information and exit
If PASSWORD is missing then it is asked interactively.
If no SALT is specified, a random one is generated.
If TYPE is 'help', available methods are printed.
Report bugs to <md+whois@linux.it>.
したがって、sha-512で指定のソルト(saltsalt)とパスワード(secretsecret)をrounds=8500のハッシュ値は↓で求められる。
$ mkpasswd -m sha-512 secretsecret saltsalt -R 8500
$6$rounds=8500$saltsalt$3zc0kGtL5yiczEr5gjeMvryyTpemGuYgwOP9LwtXh0sd8f0aNtPKJ2EipeMSxFXf3YFtg0pJx3MqcCLodK.XX.