動機
cron
等で定期的に認証のあるサーバーにhttp(s)アクセスするpythonスクリプトを書こうと思ったのだが、認証情報(http_user
,http_password
)を安全に保管する方法がないだろうか?と考えた。
- 一般的な方法は、スクリプトとは別のファイルに保存して、ファイルのアクセス権限をファイルオーナーのみにする方法(
chmod 0600 ....
)である。 この場合、root権限のある人には見えてしまいそう。あと、ファイルバックアップとかでファイルが複製されたりする可能性とかも考えると、情報管理的に心配。 - これを避けるためには、暗号化してファイルに書き込む必要がある。Pythonのスクリプトだと、ソースコードが丸見えで暗号化/復号化の方法を隠すことができないので、暗号化されたデータとともに復号のための情報(暗号化の際に用いたキーとか)が揃ってしまうと数行のスクリプトを書けば復号できてしまうので、これらの情報をファイルにセーブすると前項と同じ問題が生じる。
- 上記の問題を避けるには、暗号化キーなどをファイル等にセーブするのではなく、人間が記憶して(コマンドラインオプション等で)スクリプトを実行する際に与える、といったことが必要。ただ、スクリプトを
cron
等で定期的に自動起動したい場合には別の手を考える必要がある。なにか別の方法が必要。 - たとえば,
SSH
経由のrsync
などで定期的にファイルを同期したい場合、ssh-agent
に鍵をキャッシュさせたりする。最初に一度ssh-add
をする必要があるが、ssh-agent
に鍵が登録されている間は,ssh-agent
のクライアントは鍵の利用が可能である。これを利用できないか? -
ssh-agent
から得られるのは秘密鍵なのでデータを直接暗号化することには適さないが、あらかじめ決めたデータを秘密鍵で署名することで、データを暗号化するためのキーの生成にはつかえるのでは?
と思ったところで、pythonにssh-agent
のクライアントとしてつかえるモジュールparamiko
を見つけたので、ssh-agent
にある鍵を使って暗号/復号キーを生成して、データを暗号/復号するライブラリを作成することにした。 自分は暗号の専門家ではないので本当に安全な方法かわからないので、簡易的なデータのモザイク加工程度だと思って利用することにする。
暗号/復号の具体的な方法の概要
上記で思いついた方法を(絵で)まとめると下記。(図中のEncDSUtil....()
は、後述の自作モジュールの関数)
復号化の手順をまとめると、下記。 暗号化されたデータと、暗号化の際に用いたMaster Key Phraseは平文で保存しておいたとしても、ssh-agent
から秘密鍵をもってこないと復号できないはずで、そのためには ssh-agent
に秘密鍵をデコードして渡すためのパスフレーズが必要 である。(実用的には、このほかにssh-agent
に複数の登録されていることがありうるので、どの鍵を使うかという情報も必要。) そのため ssh-agent
の信頼性と同程度には信頼できる気がする。(あくまでも無保証)
ちなみにRSA暗号ではうまくいくが、ed25519
では、署名が必ずしも一定値でないのでこの目的には適さない模様。
実装(1)
この目的で使うRSAの鍵ペアとしては、普段ssh
コマンドで使う鍵ペアとは別途用意するのが無難に思われる。
paramiko
は、ssh-agent
にある鍵のリストを作る機能はあるが、
この目的ではいくつかの鍵を使い分けることにもなると思うので、まず、python
でSSH
の鍵を管理するモジュールsshkeyring
を作成した。
- ファイル置き場
- インストール方法
sample
% pip install sshkeyring
- 機能
- 必要な場合にSSH鍵ペアの自動生成
- 必要な場合にSSH鍵を
ssh-agent
に登録 - 必要な場合に
ssh-agent
を起動 -
ssh-agent
から特定のIDの秘密鍵を取得 - (
ssh-agent
を使わず、ローカルの鍵ファイルの利用することにも対応)
paramiko
を拡張してみた件については、別記事にあります。
実装(2)
実際にデータを暗号化/復号化する機能を持ったクラスを提供するモジュールenc-ds
を作成した。
- ファイル置き場
- インストール方法
sample
% pip install enc-ds
- 機能:
-
sshkeyring
を使って、SSH鍵を使えるようにセットアップ - 多層の
dict
型、list
型による木構造のデータ構造(enc_ds.DataTree
クラス)のバッファをもつ。 - バッファにあるデータの暗号化、復号化
- 暗号化されたデータをファイルから読み込んで復号
- データを暗号化してファイルに保存
- ファイル形式としては、
json
形式,yaml
形式,toml
形式,ini
形式および、これらの圧縮ファイル(bzip2
,xz
,gzip
)に対応。
-
詳しい使い方の説明はこれから整備したいとおもいますが、サンプルスクリプトenciphered_datastorage
(.py
)も同梱しています。このサンプルスクリプトは、コマンドラインツールとして、データやファイルの暗号化・復号が可能です。
初回起動時には、
% ./bin/enciphered_datastorage -p ...somewhere... -v -i id_rsa_test 'Sample Plain Text' 'Second Data'
...
[Enc_ds.enc_ds.setup_sshkeyinfo:744] Info : Key ID in use : id_rsa_test, Key Type in use : rsa
[SSHKeyInfo.set_passphrase:108] private key passphrase:
[SSHKeyRing.ssh_add_keyinfo:308] Info ssh-add : ssh-agent ( key id: id_rsa_test, type: rsa, sock : *******)
...
とSSH鍵をつくるためのパスフレーズのプロンプトが出て、入力するとRSA鍵ペアができて、ssh-agent
が登録されます。2回目以降はssh-agent
に登録されている鍵が使われます。上記の例では、
---------------------------------------------------------------
(0) Input : Sample Plain Text
---------------------------------------------------------------
(0) Enciphered : b'\x00\x00\x00\x10\xab\x85\x18\x9d\xa3Op\xa9$\x1eD\x1fuEY\x9e\xa4~\xf5G/\xd2\xb9\xbf\xa3\xbd\xd2\x10&\xb6XS\xbb\xf1Y\x94\xcd\xcdwnrX\x1a\x94Can\x10\xe7+#YO\xde\xa6\xe8\xcc*\xefdx\x8e\x9d\xc8\x1d'
(0) Enciphered data : {'data': b'\x00\x00\x00\x10\xab\x85\x18\x9d\xa3Op\xa9$\x1eD\x1fuEY\x9e\xa4~\xf5G/\xd2\xb9\xbf\xa3\xbd\xd2\x10&\xb6XS\xbb\xf1Y\x94\xcd\xcdwnrX\x1a\x94Can\x10\xe7+#YO\xde\xa6\xe8\xcc*\xefdx\x8e\x9d\xc8\x1d', 'iv': b'\xe7\xff\x89+J\xae}\xa7S\x91\xa3\xd0', 'salt': b'\xb8\xcf\xa9+}\xb3\xf8Lw%\x02M\xac\x16\x9f:\xa5W4V\x07p\x8eMP\t+K\xcfI#`', 'mkey': 'A0/WJqvYO6VtnpmXH/cUGj5s2a5ACw8minvHvhemLnw=', 'pkey': '8e04UCkhFk+eW7nM8ewH9g=='}
(0) Deciphered (Check) : Sample Plain Text
---------------------------------------------------------------
[EncDSUtil.CheckBytesLength:52] New 256-bytes data is created recreated
---------------------------------------------------------------
(1) Input : Second Data
---------------------------------------------------------------
(1) Enciphered : b'\x00\x00\x00\x10\x9dL\x10\x07\x15\x17\xe6\x081\xa8\xbd\x19I@\xdaR\xf7a\x81K\x97\xb6c\xe8 G\xb2\xeav\xb7\x1a\xe0\xf6\xcf\x1f\xfe\x82\x03X\n[\xdbT(}\xc4\x9c\x8d\xfa\x05\xba\x0b\x96\xf3z\xbc/\xda\xe4'
(1) Enciphered data : {'data': b'\x00\x00\x00\x10\x9dL\x10\x07\x15\x17\xe6\x081\xa8\xbd\x19I@\xdaR\xf7a\x81K\x97\xb6c\xe8 G\xb2\xeav\xb7\x1a\xe0\xf6\xcf\x1f\xfe\x82\x03X\n[\xdbT(}\xc4\x9c\x8d\xfa\x05\xba\x0b\x96\xf3z\xbc/\xda\xe4', 'iv': b'\xbb\xdd\xe4yH+\xea\xb1\xd5T)f', 'salt': b'a\x95j\xa6~\x03\xb4\xfd\xe0F\xa3\x92\x0cq&\x1a\x00\x9a\x96"\x1aXD7X\xf1}d\x82[\x83\xbf', 'mkey': 'A0/WJqvYO6VtnpmXH/cUGj5s2a5ACw8minvHvhemLnw=', 'pkey': '8e04UCkhFk+eW7nM8ewH9g=='}
(1) Deciphered (Check) : Second Data
---------------------------------------------------------------
といった感じで、引数の文字列を暗号化し、また復号できることをチェックします。また、
--encode-mode
か --decode-mode
というオプション引数をつけると、ファイルIOに対応します。
まとめ
汎用性の高いクラスになった気もしますが、説明が足りないですね。おいおい使用例を作っていこうと思います。