ファイル、サーバ、メモリ、様々な動作形態がとれてプロトタイピングや、配布アプリの組み込みDBとして便利に使えるPure Javaデータベースの「H2」。
- zipファイルでWin10にインストール。
- H2 Databaseコンソール(Webブラウザ)起動。
- H2にユーザ情報テーブル作成。
- データ追加。(パスワードはH2のSHA256ハッシュ関数で保存)
- JDBCでH2に接続、平文パスワードをJavaでSHA256化した値と比較。
こんな感じのことを試してみようと思います。
目次
環境
- Windows10
- Java 8(Eclipse同梱ではなくOSにインストール)
- Eclipse 202003(Java11)
- H2 Database ver 1.4.199
H2はJavaで作られていて、コマンドプロンプトから実行します。
コマンドプロンプトからJVMを起動できるよう、OSにJDKをインストールしてjavaコマンドを実行出来るようにしておきます。
(*)2020/08時点でH2の最新は1.4.200ですが、私の環境だとDBファイルを作る際に以下のエラーに遭遇しました。
一般エラー: "java.lang.IllegalStateException: Unable to read the page at position 1967576057930752 [1.4.200/6]"
General error: "java.lang.IllegalStateException: Unable to read the page at position 1967576057930752 [1.4.200/6]" [50000-200] HY000/50000 (ヘルプ)
githubのissueを見るとOpenなままです。
この為、一つ下のバージョン1.4.199を使用します。(調査するのがめんどくさい)
H2 Databaseの構築
インストールからデータ追加まで。
Windows10にインストール
インストーラ形式とzipファイル形式があります。
今回はAll Platform用のzipファイルを解凍してC:\opt\h2に置きました。
C:\opt\h2>dir
2020/08/14 23:45 <DIR> bin
2019/03/13 14:58 385 build.bat
2019/03/13 14:58 546 build.sh
2020/08/14 23:45 <DIR> docs
2020/08/14 23:45 <DIR> service
2020/08/14 23:45 <DIR> src
コンソール(Webブラウザ)起動
binディレクトリに入り、h2.batを実行。
C:\opt\h2>cd bin
C:\opt\h2\bin>h2.bat
ブラウザが起動し、8082ポートでコンソール開始画面が開きます。
今回はHDDに作成されるDBファイルにTCP接続するサーバモードで使います。
「接続」するとホームディレクトリ直下にtest.mv.dbファイルが出来上がり、SQLを実行できる画面に遷移します。
SQL編集画面からテーブルを作成
表示されたSQL編集画面で以下のテーブルを作成してみます。
- users:ユーザ情報(メール、パスワードハッシュ、権限)
- role:権限マスタ
/* 権限マスタ */
create table role(
id int primary key,
name varchar(50)
);
/* ユーザ情報 */
create table users(
mail varchar(255) primary key,
name varchar(255),
password varchar(255),
role int,
foreign key(role) references role(id)
);
作成したテーブルにデータを追加
roleテーブル:1:user(一般利用者)、2:admin(管理者)
usersテーブル:パスワードはH2のhash関数でSHA256ハッシュ化
H2の関数詳細は公式を参照。
insert into role values(1, 'user');
insert into role values(2, 'admin');
insert into users
values(
'yamada@hoge.com',
'山田太郎',
hash('sha256', stringtoutf8('password1')),
1
);
insert into users
values(
'suzuki@foo.com',
'鈴木一郎',
hash('sha256', stringtoutf8('password2')),
2
)
;
selectして追加されたか確認。
一般利用者の山田さん、管理者の鈴木さんが登録されました。
山田さんのパスワードは「password1」、鈴木さんのパスワードは「password2」でしたがハッシュ化されてシステム側では元のパスワードがなんだったのか分からなくなっています。
EclipseからJDBCでH2に接続
H2側のデータが整ったのでJavaプログラムから接続してみます。
mavenプロジェクトを作ってH2の依存を追加
(gradleでもいいですが)EclipseでMavenプロジェクトを作成。
mvnrepositoryからH2のドライバを含むjarを探し、pom.xmlに追加します。
(ついでにJava11でビルドされるようにmaven-compiler-pluginも設定してます)
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
(snip)
<!-- java11でコンパイル -->
<build>
<finalName>h2</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<inherited>true</inherited>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- H2のJDBCドライバorg.h2.Driverクラスを含むjarファイル -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.199</version>
</dependency>
(snip)
</dependencies>
</project>
DBハッシュ値とJavaでSHA256化した値を比較
単純なJDBCプログラムでH2から山田さんのハッシュされたパスワードを取得、Javaでハッシュしたパスワードと比較してみます。
package sample.h2;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* Hello world!
*
*/
public class App {
public static void main(String[] args) throws SQLException, NoSuchAlgorithmException {
String jdbcUrl = "jdbc:h2:tcp://localhost/~/test";
String jdbcUser = "sa";
String jdbcPassword = "";
// DB値と比較するyamadaの平文パスワード
String userPassword = "password1";
// 1.usersテーブルからyamadaのレコードを取得
try (Connection conn = DriverManager.getConnection(jdbcUrl, jdbcUser, jdbcPassword)) {
conn.setAutoCommit(false);
String sql = "select * from users, role where users.role = role.id and users.mail = 'yamada@hoge.com'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
String mail = null;
String name = null;
String password = null;
while (rs.next()) {
mail = rs.getString(1);
name = rs.getString(2);
password = rs.getString(3);
System.out.println(mail + " | " + name + " | " + password);
}
// 2.yamadaの平文パスワードをsha256ハッシュ、16進文字列化。
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(userPassword.getBytes());
byte[] sha256 = md.digest();
StringBuilder sb = new StringBuilder(2 * sha256.length);
for (byte b : sha256) {
sb.append(String.format("%02x", b & 0xff));
}
System.out.println("yamadaのパスワードハッシュ = " + sb);
// 1と2が等しいか調べる
if (sb.toString().equals(password)) {
System.out.println("パスワードは等しい");
} else {
System.out.println("パスワードは等しくない");
}
stmt.close();
}
}
}
実行結果
平文「password1」をH2でハッシュ化、Javaでハッシュ化した結果が等しいと分かりました。
yamada@hoge.com | 山田太郎 | 0b14d501a594442a01c6859541bcb3e8164d183d32937b851835442f69d5c94e
yamadaのパスワードハッシュ = 0b14d501a594442a01c6859541bcb3e8164d183d32937b851835442f69d5c94e
パスワードは等しい
まとめ
AP、DBどちらでハッシュ化しても同じ結果になることが分かりました。
お仕事で実戦投入する際はハッシュする担当はAPかDBに統一しますし、より強度を高める為にソルトを使ったり、暗号化したり、keycloakやopenstack-keystone他外部の認証トークン生成ツールを使うなどすると思います。
実践向きの検証ではないですが、どちらでハッシュ化しても同じ結果になることを知っておくのは運用面を考えると良いことかな、と。
可搬性にも優れていますし、試作の時点ではH2でも十分代替が効きそうです。