Javaで生成したハッシュ値がおかしかった話

ある文字列を Java でハッシュ化しようとしてこんな処理を書きました。
今回の件には関係ないですが、アルゴリズムは「SHA-256」です。

String message = "メッセージ";

MessageDigest md;
try {
	md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
	throw new RuntimeException(e);
}

md.update(message.getBytes(Charset.forName("UTF-8")));

StringBuilder sb = new StringBuilder();
for (byte b : md.digest()) {
	sb.append(Integer.toHexString(b & 0xff));
}

System.out.println(sb);

すると結果は・・・
「94d14c42c7f3639f698c5ecd65599432f84545bee72d82edda144b8c14a78c」
でした。

でもよく見るとSHA-256でハッシュ化しているのに62文字しかない・・・。
(本来は64文字あるはず)

そこで、オンラインのハッシュ計算サイトで同じ計算をしてみました。
結果は、
「94d14c42c7f3639f698c5ecd655994032f84545bee72d82edda1044b8c14a78c」

なるほど、「0」が出力されていなかったんですね。
理由を探ってみます。

for 文の中で出てくる「b & 0xff」には「0 ~ 255」の整数が入ります。
これを「Integer#toHexString」を用いて16進数の文字列に変換する処理です。
この「16進数の文字列に変換する処理」に問題がありました。

ここで、整数で「0 ~ 255」というのは16進数では「0 ~ ff」となります。
「00 ~ ff」ではありません。

もうお分かりでしょうか。

整数で「0 ~ 15」の時は、本来「00 ~ 0f」として変換してほしかった文字列が、
「0 ~ f」として変換されてしまっていたんですね。
「0 埋めとしての 0」が欠けた結果、「0」の個数分短くなってしまったというわけです。

0 埋めをしたい場合は「String#format」を利用するのが便利かと思います。
先ほどのコードを書き直しましょう。

String message = "メッセージ";

MessageDigest md;
try {
	md = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
	throw new RuntimeException(e);
}

md.update(message.getBytes(Charset.forName("UTF-8")));

StringBuilder sb = new StringBuilder();
for (byte b : md.digest()) {
	sb.append(String.format("%02x", b & 0xff)); // String#format に修正
}

System.out.println(sb);

これでめでたく64文字のハッシュ値が出力されるようになりました。