Codeigniterで保存されているパスワードを復号化してDjangoに移行してみよう
いきなりですが
こちらのサイトによると最近流行りのPHPフレームワークTOP2は Laravel, Phalcon なんだとか
http://www.sitepoint.com/best-php-frameworks-2014/
多分昨今のPHP界隈は Codeigniter から Laravel, Phalcon あたりに旧システムを移植する話がどんどん増えてきてる頃合い(良く分かってないです)
せっかく移行するなら言語を変えたいと思ってる人もいてもいいはずですよね。
んでCodeigniterのパスワードは復号化できるのでPythonで復号化処理を書いちゃえばみなさんの重い腰もあがりやすいはず (Djangoは復号化出来ない, パスワードチェックはhash値の比較だったかな)
もくじ
いろいろ端折っているのでimportエラー他はそれなりに出ますよ
Codeigniter復号化処理
まず下記のような復号化処理を定義しておきます
# -*- coding: utf-8 -*- import base64 import logging import hashlib import itertools import mcrypt # XXX: pip install python-mcrypt logger = logging.getLogger(__name__) class DecodeError(Exception): """ CrypterDecode error """ class Codeigniter(object): def __init__(self, bit='rijndael-256', mode='cbc'): # AES:Rijndael 256bit, CBC modeを使用 self.crypter = mcrypt.MCRYPT(bit, mode) def encode(self): raise NotImplementedError("Not implemented error: Codeigniter.encode") def decode(self, string, key='my-secret-key'): """ Codeigniterで保存されているパスワードの復号化 """ data = '' hashkey = hashlib.md5(key).hexdigest() try: ## Codeigniter独自(っぽい)の復号化前処理 # data_str: 元のバイト列を返す # key_str: hash文字数が超えたらcycleして初めからhash文字を返す iters = zip(base64.b64decode(string), itertools.cycle(hashlib.sha1(hashkey).hexdigest())) for data_str, key_str in iters: temp = ord(data_str) - ord(key_str) if temp < 0: temp += 256 data += chr(temp) except TypeError as err: logger.exception(err) raise DecodeError(str(err)) ## 以下mcrypt使用時のお約束 init_vect = data[0:self.crypter.get_iv_size()] data = data[self.crypter.get_iv_size():] try: self.crypter.init(hashkey.ljust(24, '\0'), init_vect) except ValueError as err: logger.exception(err) raise DecodeError(str(err)) return self.crypter.decrypt(data)[0:32].rstrip('\0')
実行方法
>>> codeigniter = Codeigniter()
>>> codeigniter.decode(encrypted_password)
'output raw password'
Django inspectdb
Command
次に app の作成, Database から Django のモデル定義を生成するコマンドを実行
http://docs.djangoproject.jp/en/latest/ref/django-admin.html#inspectdb
$ python manage.py startapp egg $ python manage.py inspectdb > egg/models.py
生成後のディレクトリ構成は下記のようになっている
$ tree . ├── development.db ├── egg │ ├── __init__.py │ ├── models.py │ ├── tests.py │ └── views.py ├── ham │ ├── __init__.py │ ├── __init__.pyc │ ├── settings.py │ ├── settings.pyc │ ├── urls.py │ └── wsgi.py └── manage.py
South
パッケージ
migration ツールの South を実行して Codeigniter のDatabase定義を修正する準備
$ python manage.py schemamigration [django_app] --initial
これで、定義は終了したのでmodelsを修正すれば、south がDatabaseの改修をしてくれます。
$ python manage.py schemamigration [django_app] --auto
$ python manage.py migrate
Django Custom User
最後に先ほど定義した復号化処理を Django user model へ差し込めば移行処理が完了
# -*- coding: utf-8 -*- from django.db import models from django.utils import timezone from django.contrib.auth import hashers from django.utils.encoding import python_2_unicode_compatible from . import crypter @python_2_unicode_compatible class BaseUser(models.Model): """ 各UserModelの親クラス passwordのアルゴリズムを旧フレームワークから移植している """ encrypted_password = models.CharField(u'パスワード', max_length=128L) last_login = models.DateTimeField(u'ログイン日時', default=timezone.now) is_active = True REQUIRED_FIELDS = [] class Meta: abstract = True def get_username(self): """ AccountのID識別子を返却 """ return getattr(self, self.USERNAME_FIELD) def __str__(self): return self.get_username() def natural_key(self): return (self.get_username(), ) def is_anonymous(self): return False def is_authenticated(self): return True def set_password(self, raw_password): """ パスワードを設定する """ self.encrypted_password = hashers.make_password(raw_password) def check_password(self, raw_password): """ raw passwordのチェック 旧passwordが正しい場合は即Django暗号化方式のパスワードへ更新する """ def setter(raw_password): """ password更新 """ self.set_password(raw_password) self.save(update_fields=["encrypted_password"]) if self.has_usable_password(): # Django password return hashers.check_password(raw_password, self.encrypted_password, setter) else: # Old framework password try: result = crypter.codeigniter.decode(self.encrypted_password) == raw_password except crypter.DecodeError: return False if result: setter(raw_password) return result def set_unusable_password(self): """ 有効なhash値にならないパスワードをセット """ self.encrypted_password = hashers.make_password(None) def has_usable_password(self): """ 有効なパスワードか """ return hashers.is_password_usable(self.encrypted_password) def get_full_name(self): raise NotImplementedError() def get_short_name(self): raise NotImplementedError() def _get_encrypted_password(self): return self.encrypted_password def _set_encrypted_password(self, value): self.encrypted_password = value password = property(_get_encrypted_password, _set_encrypted_password)
実行方法
テキトーにrunserver
してログインしてみてください
$ python manage.py runserver
うん、これでDjangoに移行できるかな?
稚拙なことしか書いてないですが、是非トライしてみてください。