Webセキュリティの小部屋

Twitter のフォローはこちらから Facebook ページはこちら RSSフィードのご登録はこちらから
公開日:2013年2月27日
最終更新日:2020年8月13日

安全なパスワードの保存方法(Java編)

以下の記事の内容を受け、Java で salt(ソルト)を使用したパスワードと、salt にストレッチングを組み合わせたパスワードを取得する実装を示します。

salt にストレッチングを組み合わせる方法ですが、その実装方法としては専用のハッシュ関数を使用することが現在は推奨されます。

Javaでパスワードのハッシュ値を取得するには、Java の Web アプリケーションフレームワークのデファクトスタンダードである Spring Framework の Spring Security を使用するのがよいでしょう。広く使用されていることと、開発が活発なのがその理由です。

サンプルコードは BCrypt と PBKDF2 の2通りの実装になります。

pom.xml の設定

Spring Framework でパスワード用のハッシュ関数を使用するには、pom.xml に以下の設定を追加する必要があります。

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
</dependency>

BCrypt の実装

BCrypt の Spring Security の実装は以下のようになります。Spring Boot での実装ですが、パスワードの部分は非常に簡単に実装できることが分かると思います。

package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Controller
public class HelloSpringBootWebController {

	// BCrypt
	PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
	
	@RequestMapping(value="/", method=RequestMethod.GET)
    public ModelAndView index(ModelAndView mv) {
        mv.setViewName("index");
        
        // パスワードを設定
        String password = "password";
        
        // ハッシュ化パスワード取得
        String hash = GetHashedPassword(password);
        
        // パスワードチェック
        CheckHashedPassword(password, hash);
        
        return mv;
    }
    
    
    // ハッシュ化パスワード取得
    private String GetHashedPassword(String password) {
        String hash = passwordEncoder.encode(password);
        
        System.out.println("ハッシュ値 : " + hash);
        
        return hash;
    }
    
    // パスワードチェック
    private void CheckHashedPassword(String password, String hash) {
    	if (passwordEncoder.matches(password, hash)) {
    		System.out.println("パスワードが一致しました。");
    	} else {
    		System.out.println("パスワードが一致しません。");
    	}
    }
}

このコードの実行結果は、以下のようになります。
ハッシュ値 : $2a$10$PwzoVb6xybS821tPLsG0je3RrFGJs.M0cJ9.njr0JixXl4zcL21r2
パスワードが一致しました。

PBKDF2 の実装

PBKDF2 は BCrypt とほとんど同じ実装で実現できます。
実装コードは以下のようになります。

package com.example.demo;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Controller
public class HelloSpringBootWebController {

	// PBKDF2 
	PasswordEncoder passwordEncoder = new Pbkdf2PasswordEncoder();
	
	@RequestMapping(value="/", method=RequestMethod.GET)
    public ModelAndView index(ModelAndView mv) {
        mv.setViewName("index");
        
        // パスワードを設定
        String password = "password";
        
        // ハッシュ化パスワード取得
        String hash = GetHashedPassword(password);
        
        // パスワードチェック
        CheckHashedPassword(password, hash);
        
        return mv;
    }
    
    
	// ハッシュ化パスワード取得
    private String GetHashedPassword(String password) {
        String hash = passwordEncoder.encode(password);
        
        System.out.println("ハッシュ値 : " + hash);
        
        return hash;
    }
    
    // パスワードチェック
    private void CheckHashedPassword(String password, String hash) {
    	if (passwordEncoder.matches(password, hash)) {
    		System.out.println("パスワードが一致しました。");
    	} else {
    		System.out.println("パスワードが一致しません。");
    	}
    }
}

このコードの実行結果は、以下のようになります。
ハッシュ値 : adc7c5d3843c66d5f5e952d82ea3095cf1d1ca464b52711ce1d83d98c025029d7e6b3a6bdc21f430
パスワードが一致しました。

 

推奨されなくなった方法

2019年1月現在、パスワードのハッシュ化は独自実装するのではなく、ハッシュ関数を使用することが推奨されています。

そのため、以下のコードは現在では推奨されないコードになります。

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SafePassword {

  private static int STRETCH_COUNT = 1000;

  /*
   * salt+ハッシュ化したパスワードを取得
   */
  public static String getSaltedPassword(String password, String userId) {
    String salt = getSha256(userId);
    return getSha256(salt + password);
  }

  /*
   * salt + ストレッチングしたパスワードを取得(推奨)
   */
  public static String getStretchedPassword(String password, String userId) {
    String salt = getSha256(userId);
    String hash = “”;

    for (int i = 0; i < STRETCH_COUNT; i++) {
      hash = getSha256(hash + salt + password);
    }

    return hash;
  }

  /*
   * 文字列から SHA256 のハッシュ値を取得
   */
  private static String getSha256(String target) {
    MessageDigest md = null;
    StringBuffer buf = new StringBuffer();
    try {
      md = MessageDigest.getInstance(“SHA-256″);
      md.update(target.getBytes());
      byte[] digest = md.digest();

      for (int i = 0; i < digest.length; i++) {
        buf.append(String.format(“%02x”, digest[i]));
      }

    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }

    return buf.toString();
  }
}

参考

Webアプリケーションセキュリティに関する記事は、以下のページにまとまっています。ぜひご確認ください。


スポンサーリンク





カテゴリー:Webアプリケーションセキュリティ対策

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA