Java と Javascript で md5ハッシュ が一致しない現象を調査してみた

JavaJavascriptMD5ハッシュが一致しない現象に遭遇した時の記録です。

はじめに

Androidアプリで文字列の一致を確認するために、MD5ハッシュを取得し比較する処理を使っていました。

アプリ内では、平文が与えられるとJavaJavascriptでそれぞれMD5ハッシュを取得し、Javascript 側で比較を行います。

参考:MD5とは

ja.wikipedia.org

具体的な構成は以下のようになります。

構成

発生した現象

特定の平文を与えるとJavaJavascriptで取得できるMD5ハッシュが異なっているため、比較の判定でNGが返ってきました。

平文

test

Javascript側の返り値

098f6bcd4621d373cade4e832627b4f

Java側の返り値

98f6bcd4621d373cade4e832627b4f6

Javascript側の実装

以下のOSSを使ってMD5ハッシュを取得します。

github.com

JavaScript-MD5を外部ファイルとして読み込んでから、以下のコードを実行することで平文のMD5ハッシュを取得します。

入力

console.log(md5("平文"));

コンソール出力

fc1e8849b94f899c2dbf68d8a4ceac4e

Java側の実装

MessageDigestを使ってMD5ハッシュを取得します。

docs.oracle.com

JavaMD5ハッシュ文字列取得関数は以下のような関数に"message"を渡して実行することで、平文からMD5ハッシュを取得します。

// メッセージダイジェストインスタンスを作成
MessageDigest md5 = MessageDigest.getInstance("md5");
// 平文からMD5ハッシュのバイト列を取得
byte[] md5_byte = md5.digest(message.getBytes());
// BigIntegerに変換
BigInteger md5_int = new BigInteger(1, md5_byte);
// BigIntegerから16進数の文字列に変換
return String.format("%02x", md5_int);

入力

System.out.println(getMD5Hash("平文"));

コンソール出力

fc1e8849b94f899c2dbf68d8a4ceac4e

原因

この現象の原因はJava側の実装です。 Java側の実装としては以下のような動きになっていました。

数値変換

MessageDigestインスタンスの出力はバイト列のため、一度10進数に変換しています。この時、先頭バイトの上位4bitが「0000」の場合16進数変換で先頭の0が消されていることがわかりました。

先頭の0が消滅

対応

MD5ハッシュは固定長の128bitのため、16進数変換した場合必ず文字長は32文字になります。そこでフォーマットで0埋めを行うように指定している文字数を、32文字に変更することで対応しました。

// メッセージダイジェストインスタンスを作成
MessageDigest md5 = MessageDigest.getInstance("md5");
// 平文からMD5ハッシュのバイト列を取得
byte[] md5_byte = md5.digest(message.getBytes());
// BigIntegerに変換
BigInteger md5_int = new BigInteger(1, md5_byte);
// BigIntegerから16進数の文字列に変換
- return String.format("%02x", md5_int);
+ return String.format("%032x", md5_int);

おわりに

規約やプロトコル等の形式が定められていることの重要性を知ることができました。利用する対象の形式やルールを確認し、正しく処理を作ることを心掛けていきたいと思います。