JavaとJavascriptでMD5ハッシュが一致しない現象に遭遇した時の記録です。
はじめに
Androidアプリで文字列の一致を確認するために、MD5ハッシュを取得し比較する処理を使っていました。
アプリ内では、平文が与えられるとJava、JavascriptでそれぞれMD5ハッシュを取得し、Javascript 側で比較を行います。
参考:MD5とは
具体的な構成は以下のようになります。
発生した現象
特定の平文を与えるとJava、Javascriptで取得できるMD5ハッシュが異なっているため、比較の判定でNGが返ってきました。
平文
test
Javascript側の返り値
098f6bcd4621d373cade4e832627b4f
Java側の返り値
98f6bcd4621d373cade4e832627b4f6
Javascript側の実装
JavaScript-MD5を外部ファイルとして読み込んでから、以下のコードを実行することで平文のMD5ハッシュを取得します。
例
入力
console.log(md5("平文"));
コンソール出力
fc1e8849b94f899c2dbf68d8a4ceac4e
Java側の実装
MessageDigestを使ってMD5ハッシュを取得します。
JavaのMD5ハッシュ文字列取得関数は以下のような関数に"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が消されていることがわかりました。
対応
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);
おわりに
規約やプロトコル等の形式が定められていることの重要性を知ることができました。利用する対象の形式やルールを確認し、正しく処理を作ることを心掛けていきたいと思います。