• 2019年5月2日木曜日
アリスト戦記
アリスト戦記 https://blog.aristo-solutions.net/2019/05/javatemplatebatchjava.html

Javaで文字列ハッシュ作成

昨今はセキュリティに対する意識が高まっており、パスワードを平文で保存するなど絶対NGとされている。
それでも大流出祭りをやらかすサービスもあるが。(;´^ω^`)

まあ、最近のフレームワークは「パスワードのハッシュ化作業」を内部で自動的にやってくれることも多いから自分で意識的にハッシュを作成するケースは減ってきているんだけど、それでも時々、自力でハッシュ化しなければならない時もある。

そんな時の為にハッシュ化ソースを予め置いておく。

どうやらJava標準搭載の機能だけで実装出来るようだ。
新しくライブラリを追加する必要は無い。

対象アルゴリズム

  • MD2
  • MD5
  • SHA-1
  • SHA-256
  • SHA-384
  • SHA-512
  • HmacSHA256

このうち、「HmacSHA256」だけは少々毛並みが違う。

「SHA256」は暗号化アルゴリズム本体なのに対し、「HMAC」とはその「使い方」とでも言おうか。

調べたところによると、「SHA256」やその他の方法でハッシュ化しても、ハッシュの衝突を利用して数学的テクニックを駆使した特殊なアタックで破られうる危険性が指摘されている。

対して、「HMAC」というのは、「元々の文字列の半分だけ使ってSHA256ハッシュ化、残りを使ってもう一回SHA256ハッシュ化」という、まあ何かセコいテクニックを使うと、この数学アタックでは破れなくなる、というものらしい。

セキュリティ強度が高いため、最近のハッシュ化はHMAC256の一択に近い。

だから使うソースはHMAC256だけってことになるかもしれないが、一応、全パターンを置いておこう。

ハッシュ化ユーティリティ

これがソースです。
気軽に持って行って下さい。

/**
 * ハッシュ文字列作成ユーティリティ
 *
 * @author アリストマスター
 *
 */
public class MessageDigestUtils {

   /**
    * MD2でハッシュ化する。
    *
    * @param text ハッシュ化テキスト
    * @return
    * @throws NoSuchAlgorithmException
    */
   public static String messageDigestMD2(String text) throws NoSuchAlgorithmException {

      byte[] cipher_byte;
      MessageDigest md = MessageDigest.getInstance("MD2");
      md.update(text.getBytes());
      cipher_byte = md.digest();
      StringBuilder sb = new StringBuilder();
      for (byte b : cipher_byte) {
         sb.append(String.format("%02x", b & 0xff));
      }

      return sb.toString();

   }

   /**
    * MD5でハッシュ化する。
    *
    * @param text ハッシュ化テキスト
    * @return
    * @throws NoSuchAlgorithmException
    */
   public static String messageDigestMD5(String text) throws NoSuchAlgorithmException {

      byte[] cipher_byte;
      MessageDigest md = MessageDigest.getInstance("MD5");
      md.update(text.getBytes());
      cipher_byte = md.digest();
      StringBuilder sb = new StringBuilder();
      for (byte b : cipher_byte) {
         sb.append(String.format("%02x", b & 0xff));
      }

      return sb.toString();

   }

   /**
    * SHA1でハッシュ化する。
    *
    * @param text ハッシュ化テキスト
    * @return
    * @throws NoSuchAlgorithmException
    */
   public static String messageDigestSHA1(String text) throws NoSuchAlgorithmException {

      byte[] cipher_byte;
      MessageDigest md = MessageDigest.getInstance("SHA1");
      md.update(text.getBytes());
      cipher_byte = md.digest();
      StringBuilder sb = new StringBuilder();
      for (byte b : cipher_byte) {
         sb.append(String.format("%02x", b & 0xff));
      }

      return sb.toString();

   }

   /**
    * SHA256でハッシュ化する。
    *
    * @param text ハッシュ化テキスト
    * @return
    * @throws NoSuchAlgorithmException
    */
   public static String messageDigestSHA256(String text) throws NoSuchAlgorithmException {

      byte[] cipher_byte;
      MessageDigest md = MessageDigest.getInstance("SHA-256");
      md.update(text.getBytes());
      cipher_byte = md.digest();
      StringBuilder sb = new StringBuilder();
      for (byte b : cipher_byte) {
         sb.append(String.format("%02x", b & 0xff));
      }

      return sb.toString();

   }

   /**
    * SHA-384でハッシュ化する。
    *
    * @param text ハッシュ化テキスト
    * @return
    * @throws NoSuchAlgorithmException
    */
   public static String messageDigestSHA384(String text) throws NoSuchAlgorithmException {

      byte[] cipher_byte;
      MessageDigest md = MessageDigest.getInstance("SHA-384");
      md.update(text.getBytes());
      cipher_byte = md.digest();
      StringBuilder sb = new StringBuilder();
      for (byte b : cipher_byte) {
         sb.append(String.format("%02x", b & 0xff));
      }

      return sb.toString();

   }

   /**
    * SHA-512でハッシュ化する。
    *
    * @param text ハッシュ化テキスト
    * @return
    * @throws NoSuchAlgorithmException
    */
   public static String messageDigestSHA512(String text) throws NoSuchAlgorithmException {

      byte[] cipher_byte;
      MessageDigest md = MessageDigest.getInstance("SHA-512");
      md.update(text.getBytes());
      cipher_byte = md.digest();
      StringBuilder sb = new StringBuilder();
      for (byte b : cipher_byte) {
         sb.append(String.format("%02x", b & 0xff));
      }

      return sb.toString();

   }

   /**
    * SHA256HMACでハッシュ化する。
    *
    * @param text ハッシュ化テキスト
    * @param secretKey 秘密鍵
    * @return
    * @throws NoSuchAlgorithmException
    * @throws InvalidKeyException
    */
   public static String messageDigestHmac(String text, String secretKey)
         throws NoSuchAlgorithmException, InvalidKeyException {

      SecretKeySpec sk = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
      Mac mac = Mac.getInstance("HmacSHA256");
      mac.init(sk);

      byte[] mac_bytes = mac.doFinal(text.getBytes());

      StringBuilder sb = new StringBuilder();
      for (byte b : mac_bytes) {
         sb.append(String.format("%02x", b & 0xff));
      }

      return sb.toString();

   }

}

結果

動作確認すると以下のように文字列が作成されます。
ソルトを頭にくっつけるか、お尻にくっつけるか、それは呼び出し元の仕事です。

呼び出し元ソース

      //ハッシュのベースとなる文字列
      String baseText = "AristoMaster";

      //ソルト
      String salt = "hoge";

      /*
       * ベース文字列にソルトをくっつける。
       * ソルトを頭にくっつけるか、末尾にくっつけるか、それはプロジェクトの取り決めである。
       */
      String targetText = String.format("%s%s", baseText, salt);

      String hashTextMD2 = MessageDigestUtils.messageDigestMD2(targetText);
      logger.info("MD2={}", hashTextMD2);

      String hashTextMD5 = MessageDigestUtils.messageDigestMD5(targetText);
      logger.info("MD5={}", hashTextMD5);

      String hashTextSHA1 = MessageDigestUtils.messageDigestSHA1(targetText);
      logger.info("SHA1={}", hashTextSHA1);

      String hashTextSHA256 = MessageDigestUtils.messageDigestSHA256(targetText);
      logger.info("SHA256={}", hashTextSHA256);

      String hashTextSHA384 = MessageDigestUtils.messageDigestSHA384(targetText);
      logger.info("SHA34={}", hashTextSHA384);

      String hashTextSHA512 = MessageDigestUtils.messageDigestSHA512(targetText);
      logger.info("SHA512={}", hashTextSHA512);

      //秘密鍵
      String secretkey = "hogehoge";

      String hashTextHMAC = MessageDigestUtils.messageDigestHmac(baseText, secretkey);
      logger.info("HMAC={}", hashTextHMAC);

結果

  • MD2=1598b4e8f536547a3aa281b2c1fee20e
  • MD5=d4bf4c04c71aa7f7eb8d8ac0e0d84789
  • SHA1=b608b8e8cd53506415e13c7ca576759060c1482e
  • SHA256=ea0820a1a747433fb39072c73e287708b632737e0b09b4def0991870f7a945a7
  • SHA34=ff0df38b6b39a664251594fb98c6e6b833f31101202d0db976b3c21797ec6607e51ae52550d821e2a2728106ccb1941b
  • SHA512=eee01f5d07531d067e98b78fa085f255c2222e263a6fb9da9ee21ff95c0676598015876c643b41203ed109dc292c869b3473658f7969d3ef37aee4909225563c
  • HMAC=0763e7532376ce3d61a4876f857ef72582ab3647b319afbdb120ed025a6f9479

気付いたこと

何か「HMAC」だけ出力が遅い。
他のアルゴリズムが一瞬であるのに対し、HMACだけは0.5秒くらい要する。

アタックに強くなるように時間が掛かるロジックになっているということだろうか?

0 件のコメント:

コメントを投稿