ハッ~シュん!と、くしゃみが出て、思わずハクション大魔王を呼んでしまいそうな季節、いかがお過ごしでしょうか?どうも僕です。
部屋の掃除をすると決意した昨日の夜から、1mmも掃除に取り掛かれていない、そんな12/08(土)です...
というわけで、今回は、ハッシュ化のお話です、Javaでね。
話は変わりますが、常駐先の方に教えていただき、「日本Javaユーザーグループ(Japan Java User Group/JJUG)」ってグループの開催する、2018/12/15(土)イベントに申し込んじゃいました。
全部見たいけど~、
⇧ 複数の講演があるので、泣く泣く候補を絞りました。GraphQLの内容も見たかったですが...
メールで申込完了の知らせが届いてました。
難しくて理解できないとは思いますが、来週のJavaのイベントが楽しみではあります。
話が脱線してしまいましたが、レッツ、トライ~。
ハッシュ化とは?
そも、ハッシュ化とは?
Wikipediaさ~ん!
hash
- ハッシュ、ハッシュ値 - データから算出した小さな値。各データを区別・表現する目的に用いる。
- ハッシュ関数 - データからハッシュ値を算出する関数、そのコンピュータ技術。
- ハッシュテーブル(連想配列の実装方法の1つ)
- ハッシュセット(集合の実装方法の1つ)
- 暗号学的ハッシュ関数
- hash (Unix) - Unixコマンド
- ハッシュタグ
- ハッシュ関数 - データからハッシュ値を算出する関数、そのコンピュータ技術。
- 「#」記号
- 大麻を表す隠語
- ハッシュ (料理) - 牛肉などを細切れにして炒めた料理
- ハッシュハウスハリアーズ - 国際的なランニング愛好会
⇧ いろいろ候補が出てきてしまうんですが、プログラム的には、「ハッシュ関数」のくくりが該当しそうですかね。
そんじゃ、「ハッシュ関数」のWikipediaの見解はというと、
⇧ 「ハッシュ関数」を使って、『元ある値(パスワードとか?)』を加工することを「ハッシュ化」、加工した値を、『ハッシュ値』または『ハッシュ』っていうらしいですね。
Wikipediaさんの図を参考にすると、下記のようなイメージらしいです。(※「ハッシュ値」の値は、あくまでイメージです。)
で、「ハッシュ関数」で、『元ある値(パスワードとか?)』を加工した時に、『ハッシュ値』が被ってしまうことがあると...駄目ですやん...
⇧ 起こりえてしまうらしいですね...
なので、データベースとかに『パスワード』を加工して『ハッシュ値』として保存する場合は、『ハッシュ値』+『会員ID(重複なし)』みたいな形で、「一意(重複のない)」とするようなイメージですかね?
「ハッシュ関数」の具体的な用途はというと、
ということらしい。
⇧ このへんは、割愛で...
⇧ 「連想配列」のことを「ハッシュ」って...紛らわしいことを...
ちなみに、「Perl」の生みの親である、『ラリー・ウォール(Larry Wall, 1954年9月27日 - )』 さんは、『プログラマの三大美徳』を唱えた人でもあると。
ただ、私は言いたいのです。プログラマーには、お酒も必須であると。
お酒が必要な理由、それが、「バルマーピーク」さ~。
「血中アルコール濃度が0.129~0.138%のときに超人的なプログラミング能力を発揮できる」
⇧ 上記サイト様によりますと、効果のほどは...う~ん...な結果になってしまうらしいですが。「バルマーピーク」については、Swizec Teller さんという方が仰っているそうですね。
⇧ 何故、プログラマーは夜働くのかって、仰られていますが、私は、夜は眠りたい人ですかね...
とは言え、プログラマーの美徳は、
ts0818によれば、プログラマの四大美徳とは次の通りである。
プログラマに必要とされる効率や再利用性の重視・処理速度の追求・品質にかける自尊心・超人思考を言ったものである。
ts0818 - Wikipedia ← 無いけど
⇧ のようになるのかと。なんか、美徳って、難しいですね...
キン肉マンのジェロニモのように、スーパーマンロードを昇りきれば、超人に、もとい、超人プログラマーになれますかね...いや、スーパーマンロードを昇らなくても、酒を飲んだら良かですか!
ビバ!酒浸りの日々!
ただ、アルコールは筋肉を分解するという弊害もあるとか、悩ましいですね。
脱線しました。「暗号学的ハッシュ関数」 の説明では、
暗号学的ハッシュ関数(あんごうがくてきハッシュかんすう、英: cryptographic hash function)は、ハッシュ関数のうち、暗号など情報セキュリティの用途に適する暗号数理的性質をもつもの。任意の長さの入力を(通常は)固定長の出力に変換する。
と。
「メッセージダイジェスト」は、暗号学的ハッシュ関数の多数ある応用のひとつであり、メールなどの「メッセージ」のビット列から暗号学的ハッシュ関数によって得たハッシュ値を、そのメッセージの内容を保証する「ダイジェスト」として利用するものである。
と。
暗号学的ハッシュ関数は、一般的なハッシュ関数に望まれる性質や、決定的であることの他、次のような暗号学的な特性をもたなければならない。
となってますかね。
具体的には、
- 原像計算困難性 (preimage resistance)
- 第2原像計算困難性
- 強衝突耐性
- hash(m1) = hash(m2) となるような2つの異なるメッセージ m1 と m2 を探し出すことが困難でなければならない。一般に誕生日のパラドックスによって、強衝突耐性を持つためには、原像計算困難性を持つために必要なハッシュ値の2倍の長さのハッシュ値が必要である。
となっていますかね...よく分からん...。 とりあえず、『ハッシュ値』が突き止められないようにする必要があるってことですかね。時間あるときに調査で...。
ハッシュ化と暗号化と復号化と
恋しさとせつなさと心強さと 、がそれぞれ異なる意味合いを持つのと同様、
ハッシュ化と暗号化は異なるらしいと。
⇧ 上記サイト様が詳しいです。
ハッシュ化したものは、もう二度と元の状態には戻せない、もうあの日は戻らない、もう二度と戻らない日々を、俺達は走り続ける、という不可逆的な処理であると。
一方、暗号化は、復号化によって、元に戻せると、ありの~、ままの~姿見せるのよ~、という可逆的な処理であると。
分かりにくくて、すみません...イメージ図を用意しました。
■ 比較のイメージ図
ハッシュ化は、「不可逆的」
暗号化は、「可逆的」
であると。
え?ハッシュ化は、元に戻せない?じゃあ、ハッシュ化で保存した「ハッシュ値」と、再度入力された「パスワード」をどうやって比較するのさ?とお思いの貴方!素晴らしい!
そうです、『再度入力された「パスワード」』を、『ハッシュ化で保存した「ハッシュ値」』の時と同じ「ハッシュ関数」にてハッシュ化して、比較するってことになります、多分。
「ハッシュ」・「ソルト」・「ストレッチング」のコラボレーション
今のご時世、ただの「ハッシュ化」では不十分であるらしい。
⇧ 上記サイト様が詳しいです。
■ 「ハッシュ」・「ソルト」・「ストレッチング」イメージ図
ハッシュ化する際に、「ソルト」として乱数を用意し、『「元の値」 + 「ソルト」』で、ハッシュ化 すると。で、そのハッシュ化を繰り返し行うことを、「ストレッチング」っていうようです。
『ハッシュ値』が流出したとしても、『ハッシュ値』を生成の際に使用されたアルゴリズムを突き止められないようにすれば、安全ではあるということでしょうかね。
Java でハッシュ化してみる
んじゃあ、実際に、試してみますか。
⇧ 上記サイト様を参考にさせていただきました。
Eclipseで、適当に「Java プロジェクト」を作成し、
「プロジェクト名(P):」を適当に入力し、「次へ(N) >」で。
「完了(F)」で。
「クラス」ファイルも作成で。
「パッケージ(K):」「名前(M):」を適当に入力し、「public static void main(String args)(V)」にチェックし、「完了(F)」。
もう一つ、「クラス」ファイルを作成。
「public static void main(String args)(V)」にはチェックせず、「完了(F)」。
クラスファイルが用意できました。
んで、クラスファイルの内容はこんな感じ
/HashTest/src/util/PasswordUtil.java
package util; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; public class PasswordUtil { // ハッシュ化アルゴリズム private static final String ALGORITHM = "PBKDF2WithHmacSHA256"; // ストレッチング回数 private static final int ITERATION_COUNT = 10000; // 生成される鍵の長さ private static final int KEY_LENGTH = 256; public static String getHashedPassword(String password, String salt) { // PBEKeyを生成に必要な値を作成 char[] passCharAry = password.toCharArray(); byte[] hashedSalt = getSalt(salt); // PBEKeyを生成 PBEKeySpec keySpec = new PBEKeySpec(passCharAry, hashedSalt, ITERATION_COUNT, KEY_LENGTH); // 秘密鍵を扱う用 SecretKeyFactory skf = null; try { // 「PBKDF2WithHmacSHA256」アルゴリズムの秘密鍵を変換するオブジェクトの生成 skf = SecretKeyFactory.getInstance(ALGORITHM); } catch (NoSuchAlgorithmException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } // 秘密鍵のインターフェイス // すべての秘密鍵のインターフェイスを扱える SecretKey secretKey = null; try { // PBEKeyの鍵仕様で、秘密鍵の生成 secretKey = skf.generateSecret(keySpec); } catch (InvalidKeySpecException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } // 鍵(秘密鍵)を一次符号化形式 byte[] passByteAry = secretKey.getEncoded(); // 生成されたバイト配列を16進数の文字列に変換 StringBuffer sf = new StringBuffer(64); for (byte b : passByteAry) { sf.append(String.format("%02x", b & 0xff)); } return sf.toString(); } // ソルトを生成 private static byte[] getSalt(String salt) { byte[] saltBytes = null; MessageDigest messageDigest = null; try { messageDigest = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { // TODO 自動生成された catch ブロック e.printStackTrace(); } if(messageDigest != null) { messageDigest.update(salt.getBytes()); saltBytes = messageDigest.digest(); } return saltBytes; } }
/HashTest/src/controller/HashTest.java
package controller; import util.PasswordUtil; public class HashTest { public static void main(String[] args) { // 入力されたパスワードを想定 String pass01 = "password"; String pass02 = "possword"; // 入力されたユーザー名を想定 String user01 = "ts0818"; String user02 = "Larry Wall"; // パスワード、ユーザーがともに同一のパターン String hashedPass01 = PasswordUtil.getHashedPassword(pass01, user01); String hashedPass02 = PasswordUtil.getHashedPassword(pass01, user01); System.out.println(hashedPass01); System.out.println(hashedPass02); // パスワード、または、ユーザーが違うパターン String hashedPass03 = PasswordUtil.getHashedPassword(pass01, user02); String hashedPass04 = PasswordUtil.getHashedPassword(pass02, user01); System.out.println(hashedPass03); System.out.println(hashedPass04); } }
「HashTest.java」上で右クリックし、「実行」>「Java アプリケーション」で。
コンソールに結果が表示されます。
入力された情報が同一の場合は、「ハッシュ値」が同一になることが確認できました!
ただ、今回の方法は、Java 8 以上じゃないと使えないようなので、ご注意を。
結局、掃除が、まったく手付かずになってしまった...明日、心を入れ替えて、掃除に取り掛かります...たぶん...
今回はこのへんで。