blog.monophile.net

コンピュータのこととかのメモ。

山本 一彰 | Takaaki Yamamoto

東京工業大学において計算機科学と応用数学を学び、 情報科学芸術大学院大学[IAMAS]においてメディア表現を専攻し修了。 2015年にコンビネータ論理を基に計算完備な計算手法 "論理珠算"を開発し、 それを含む体系である"算道"を構成した。 その成果により、 第19回 文化庁メディア芸術祭 アート部門 新人賞 (文部科学大臣賞) を2016年に受賞。 現在はインフラエンジニアとして生計をたててている。

技術

各種システムの設計/構築/運用を承ります。

Configuration Management Ansible, Terraform, cloud-init
Cloud Platform AWS, Azure, GCP, Openstack
Openstack Keystone, Glance, Cinder(Ceph), Neutron(VLAN), Nova(QEMU), Horizon
Virtualization QEMU+KVM, LXD/LXC, Docker
OS Ubuntu, Debian GNU/Linux, CentOS, ...
Storage Ceph, GlusterFS, ZFS, btrfs, ...
Networks Tunnel(IPSec, L2TP, VXLAN, GRE), WirelessAP, ...
DB MySQL, MariaDB(Galera Cluster), MongoDB
Mail postfix, dovecot
WebApps WordPress, GitLab, MatterMost, Redmine, RainLoop, ...
Monitoring Nagios, Munin
Misc certbot, dnsmasq, ...

技術(習得中)

Orchestration Kubernetes
Openstack swift, manila, trove
OS CoreOS(Container Linux), Vyatta(VyOS), ...
Networks IPv6, BGP(quagga, calico), flannel, fan, ...
DB/KVS Redis, etcd
Monitoring Prometheus, Zabbix
DNS CoreDNS, PowerDNS
Misc MAAS, Blockchain

投稿

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を作る。

ソルトの長さを確認しておく

アルゴリズムによって使えるソルトの長さが異なるので、上記で確認しておく。

  • 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コマンドが含まれている。

$ sudo apt install whois

↑でインストールすれば、↓のように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.

参考