パスワードを適切に管理したサンプルシステム(その1)
情報システムはパスワードをDBに保管しているケースが多いですが、パスワードが生のままだったりして結構残念な作りだったりします。
ちゃんとした物が作りたい・・・!ということで、自分でログインだけの機能を持ったシステムを作ってみることにします。勿論、セキュリティには気を配って。徳丸さんなど、有識者や良書の方針、指摘を最大限取り入れて。何回で完成するかは分からないですが。
アーキテクチャ
使う言語とかフレームワークとか、それらは今回は以下のような感じにします。
- Java1.6
- MySQL5.1
- Wicket (View, Controller)
- MyBatis (Model)
- wicket-auth-roles
セキュリティ要件
最低限以下を満たせるように作ります。
- DBに生のパスワードは保存しない
- パスワードはハッシュ化する
- ハッシュ化するときはソルト付きでハッシュ化する
- ソルトはユーザごとに別にする
- ソルトは容易に推測可能なものとしない
- ソルトは十分な長さにする(ここでは20文字以上とする)
DB設計(要件1)
パスワードを保存するためのテーブルを作成します。列の長さは適当です。もう少し短くてもいいかもしれないです。
-- アカウントテーブル
CREATE TABLE ACCOUNT
(
ACCOUNT_ID VARCHAR(50) NOT NULL,
PASSWORD VARCHAR(128) NOT NULL,
EMAIL VARCHAR(256) NOT NULL,
AUTHORITY_ID DECIMAL(2) DEFAULT 0,
PRIMARY KEY (ACCOUNT_ID)
) COMMENT = 'アカウントテーブル';
ソルト生成(要件4, 5, 6)
ソルトはユーザ登録するときに生成するのが妥当だと思います。という訳で、全文は載せませんが、ユーザフォームから登録のPOSTがあったとき、サーバサイドで以下のように処理するのが良いかと思います。
public class CommonConst {
public static final String SALT = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
private CommonConst() {
}
}
public void registerUser(Account account) throws UserRegistrationServiceException {
// saltと合わせてパスワードハッシュ化
String salt = account.getAccountId() + CommonConst.SALT;
String hashPassword = getHashPassword(salt, account.getPassword());
Account record = new Account();
record.setAccountId(account.getAccountId());
record.setPassword(hashPassword);
record.setEmail(account.getEmail());
record.setAuthorityId((short) 0);
// 登録
accountMapper.insert(record);
}
アカウントID + 適当な固定値をソルトとします。ソルトの目的はレインボーテーブルによるクラックのリスク低減なので、これで目的は果たせます。
引数のAccountはフォームに入力された不完全なアカウント情報です。これに情報を付け足してinsertで登録します。
ハッシュ化(要件2, 3)
上記のコードのgetHashPasswordがハッシュ化です。以下のような感じです。
private String getHashPassword(String salt, String password) {
// saltと合わせてパスワードハッシュ化
String hashPassword = DigestUtils.sha256Hex(salt + password);
return hashPassword;
}
org.apache.commons.codec.digest.DigestUtilsを利用してハッシュ化します。もちろんソルトと結合して。
これで今回の要件は多分満たせるかと思います。登録フォームから登録するとこんなような感じでDBに登録される。
これは生のパスワードは「password」として登録したものです。ハッシュ化されて保存されています。
これにより、このACCOUNTテーブルのデータが流出してもある程度は安心だと思います。まず生のパスワードではないので、即座に生パスワードがバレるということはありません。クラッカーが8桁の文字列を全て網羅したレインボーテーブルを持っていたとしても、ソルトとくっつけて算出しているため、「password」という文字列がわれることはありません。クラッカーがソルトの存在を考えたとしても、ソルトはプログラム内に存在するのですぐには分かりません。仮にユーザID + AAA..というソルトをクラッカーが知ったとしても全てのユーザで共用できるレインボーテーブルは作成できません。ユーザごとに個別に計算するしかないので膨大な時間がかかるでしょう。
この方式の欠点は生のパスワードがDBに残らないので、パスワードを忘れた場合です。次回はパスワードリセット方式について検討します。