今回は web2py での暗号化フィールドの設定方法を説明します。
ダウンロードしたファイルを、プラグインとしてアプリケーションに登録すれば利用可能です。
web2py のハッシュ化設定
web2py で暗号化というと、バリデータでの設定によるハッシュ化を思い浮かべるのではないだろうか。例えば次のような設定だ。
1 2 3 4 5 6 | table = db.define_table('user', Field('name', 'string', notnull=True, unique=True), Field('access_id', 'string', notnull=True), Field('password', 'password'), format='%(name)s') table.password.requires=CRYPT() |
モデル定義(db.py)で、userテーブルの定義を行なっている。この中の6行目では、passwordフィールドにCRYPT()を指定し、バリデータを設定している。これはフォームで入力するpasswordの値を、MD5 の ハッシュ値に変換しデータベースに格納する。
普通は MD5 ではなく次のように、HMACによるハッシュ値を算出するように設定することが多い。
table.password.requires=CRYPT(key=Auth.get_or_create_key())
もしくは、
table.password.requires=CRYPT(key=auth.settings.hmac_key)
ハッシュ化は元の値を復元できないため、安全性が高い。用途としてはパスワードの照合など、入力値をハッシュ化して値と比較するなどに使われる。
しかし元の値を復元できる暗号化を行い、暗号をデータベースに格納したいことがある。例えば、システムのユーザIDとパスワードを暗号化して保存し、必要な時に復元し利用する場合などだ。このような場合、どうすればよいだろうか?。暗号化はweb2pyは標準では対応していない。しかし次のように、多少の設定を行えば利用できる。
web2py での暗号化データベースの利用
web2py で暗号化したデータベースを利用するには、二通りの方法がある。一つは暗号化に対応したデータベース管理システムを使用することだ。Oracle、DB2、MYSQL といった主要な RDBMS では、データベースの暗号化機能を提供している。これらを利用すればパフォーマンスなどの利点も多い。しかしデータベース固有の機能を利用するためデータベースパッケージに依存する、暗号化機能を提供していないデータベースでは利用できない、など不利な点がある。
暗号化のもう一つの方法は、web2pyでデータを暗号化した後にデータベースに格納する方法である。これはハッシュ化と同様にバリデータで設定することで実現する。今回はこの方法での設定方法を説明していくが、実は次の元ネタがある。
参考: What is the best way to encrypt stored data in web2py?
元ネタのコードは、若干おかしく正常に動作しない。また、M2Secret というパッケージを使っている。この M2Secret は内部で M2Crypto を使用している。M2Crypto は easy_install コマンドではインストールできないが、各OS向けのインストーラをダウンロードして実行すればインストール可能だ。しかし M2Crypto は Pure Python ではないため、一部環境では動作しない。
このため今回は、PyCrypto を利用する。PyCrypto の注意点としては、easy_install コマンドでインストール可能だが、コンパイラー環境が整っていないとエラーで止まるようだ。環境を整えてもよいが開発環境だけの話なら、コンパイル済みのバイナリーを幾つかのサイトで配布しているので、これらを使ってもよい。
また PyCrypto は、Google App Engine(GAE)では環境に含まれているため、設定するだけで利用可能となる(設定方法は、後ほど説明します)。
参考: importing m2crypto to google app engine
PyCrypto を利用したバリデータクラスの用意
まず最初に、PyCrypto を開発用マシンにインストールする。
次に、web2py 管理画面を開き、アプリケーションに新しいモジュール(Modules)を追加する。このモジュールは PyCrypto を使用し、web2py用のバリデータクラスを定義する。モジュールのソースは次のようになる(モジュールファイル名は crypt_aes.py )。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | from gluon import * from Crypto.Cipher import AES import base64 import os class CRYPT_AES: block_size = 32 block_size_list = (16, 24, 32) def __init__(self, key=None): self.padding = '{' if key is None: key = CRYPT_AES.get_or_create_key() elif len(key) in self.block_size_list: self.block_size = len(key) else: raise TypeError, "Key length is not allowed" self.cipher = AES.new(key) def __call__(self,value): value += (self.block_size - len(value) % self.block_size) * self.padding return (base64.b64encode(self.cipher.encrypt(value)), None) def formatter(self,value): try: return self.cipher.decrypt(base64.b64decode(value)).\ rstrip(self.padding) except: return None @classmethod def get_or_create_key(cls, filename=None, block_size=None): if block_size in cls.block_size_list: cls.block_size = block_size request = current.request if not filename: filename = os.path.join(request.folder,'private','validator.key') if os.path.exists(filename): key = base64.b64decode(open(filename,'r').read().strip()) else: key = base64.b64encode(os.urandom(cls.block_size)) open(filename,'w').write(key) return key |
このソースは、こちらのサイトからもダウンロードできる。ファイルをダウンロードした後にweb2py管理画面で、ダウンロードファイルを指定して「アップロード」すれば、簡単にモジュールをアプリケーションに追加可能だ。
次に、バリデータクラスの使い方を説明する。
暗号化バリデータクラスの使い方
暗号化用バリデータクラスは、暗号化したいテーブルのフィールドの requires 属性に、CRYPT_AES() とクラスのインスタンスを指定すればよい。設定例を次に示す。
1 2 3 4 5 6 7 8 | from crypt_aes import CRYPT_AES table = db.define_table('user', Field('name', 'string', notnull=True, unique=True), Field('access_id', 'string', notnull=True), Field('password', 'password'), format='%(name)s') table.access_id.requires=crypt_aes=CRYPT_AES() table.password.requires=crypt_aes |
これによって、access_idフィールドとpasswordフィールドは、AES で暗号化される。AESの秘密鍵は、privateフォルダの validator.key ファイルが存在する場合は、そこから読みだす。もしファイルがない場合は、ランダム値を秘密鍵にしてファイルを作成し保存する。
秘密鍵のファイルを指定したり、秘密鍵を直接渡すことも可能だ。
1 2 3 4 5 6 7 8 9 10 | import os from crypt_aes import CRYPT_AES table = db.define_table('user', Field('name', 'string', notnull=True, unique=True), Field('access_id', 'string', notnull=True), Field('password', 'password'), format='%(name)s') keyf = os.path.join(request.folder,'private','access_id.key') table.access_id.requires=CRYPT_AES(CRYPT_AES.get_or_create_key(keyf)) table.password.requires=CRYPT_AES('master-key0123456789012345678901') |
8-9行目で access_idフィールドに対して get_or_create_key クラスメソッドを使って、privateフォルダの access_id.key ファイルを秘密鍵に使用するよう指定している。また10行目では、passwordフィールドに対して直接、秘密鍵を指定している。特に秘密鍵を直接渡す場合、鍵の長さに注意する必要がある。これは AESでは、16・24・32の各バイト長で、鍵を指定する必要があるためである。
Google App Engine での暗号化バリデータクラスの利用
Google App Engine(GAE)で、暗号化バリデータクラスを利用する場合の注意点を説明する。GAE環境では PyCrypto が含まれているため、次のように app.yaml ファイルに次の設定を追加する必要がある。
libraries: - name: pycrypto version: latest
この設定を追加することによって、アプリケーションで PyCrypto が利用可能となる。
また web2py で利用する外部モジュールは web2pyの site-packages フォルダに設置したりするが、GAEでは PyCrypto を web2pyの site-packages フォルダに設置した場合、動作しなくなるようだ。このため、app.yaml の設定を行うだけにする。
最後に
今回の設定ではバリデータを使っているため、フォームや SQLTable を使用しない処理では暗号化・復号化ができません。この場合、明示的にバリデータを呼び出すメソッドを使う必要があります。これらのメソッドを幾つか挙げてみます。
- Fieldクラス - validate
- Fieldクラス - formatter
- Tableクラス - validate_and_insert
- Setクラス - validate_and_update
必要に応じて、これらのメソッドを利用してください。
この他、この記事を書くために参考にして、まだ紹介していないサイトは以下の通りです。
Mattari Diary - GAEのデータストアに、PyCryptoの暗号化した文字列を登録、呼び出してみた
Code Koala - AES Encryption in Python Using PyCrypto
X-LABO - GAE 1.6.0 : python 2.7 PyCrypto ImportError