使用中のパスワードのアルゴリズム#DelegatingPasswordEncoderの変更方法


DelegatingPasswordEncoder
春はDelegatingPasswordEncoder彼はとても善良なやつで、いろいろなアルゴリズムを同時に使うことができます.
まさか、
{bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
{noop}password
{pbkdf2}5d923b44a6d129f3ddf3e3c8d29412723dcbde72445e8ef6bf3b508fbf17fa4ed4d6b99ca763d8dc
{scrypt}$e0801$8bWJaSu2IKSn9Z9kM+TPXfOc/9bdYSrN1oD9qfVThWEwdRTnO7re7Ei+fUZRJ68k9lTyuTeUp4of4g24hHnazw==$OAOec05+bXxvuu/1qZ6NUR+xQYvYv7BeL1QxwRpY5Pc=
{sha256}97cde38028ad898ebc02e690819fa220e88c62e0699403e94fff291cfffaf8410849f27605abcbc0
このように保存された暗号文にprefixを付けてアルゴリズムを区別する方法.
Specification
このクラスのconstructorは以下の通りです.
public DelegatingPasswordEncoder(
	String idForEncode,     // (1)
	Map<String, PasswordEncoder> idToPasswordEncoder    // (2)
)
(1)idForEncode暗号文を生成する際に使用するアルゴリズムを指定する必要がある.つまり、これはデフォルトのアルゴリズムと同じです.
Stringを追加する必要があります.次のidToPasswordEncoderで使用するキーを使用できます.以下の例を詳しく見ると分かります.
(2) idToPasswordEncoder Map
  • key:アルゴリズムごとのプレフィックスを表す
  • value:このアルゴリズムの実装体
  • import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
    import org.springframework.security.crypto.password.DelegatingPasswordEncoder
    import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder
    import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder
    
    val encoder = DelegatingPasswordEncoder(
        "bcrypt",
        mapOf(
            "bcrypt" to BCryptPasswordEncoder(),
            "scrypt" to SCryptPasswordEncoder(),
            "pbkdf2" to Pbkdf2PasswordEncoder()
        )
    )
    
    val encrypted = encoder.encode("password")   // (1)
    
    encoder.matches("password", encrypted) shouldBe true 
    (1)idForEncodeは"bcrypt"で、encodeを呼び出す際に使用するBCryptPasswordEncoder()符号化する.dbには、次のものが含まれます.
    {bcrypt}$2a$10$dXJ3SW6G7P50lGmMkkmwe.20cQQubK3.HZWzG3YB1tlRy.fqvM/BG
    同じ結果を残す.
    DelegatingPasswordEncoderのソースコードから見ると:
    @Override
    public String encode(CharSequence rawPassword) {
    	return PREFIX + this.idForEncode + SUFFIX + this.passwordEncoderForEncode.encode(rawPassword);
    }
    
  • ここPREFIXは「{」、SUFFIXは「}」
  • this.passwordEncoderForEncodeidToPasswordEncoder.get(idForEncode)同じ
  • 低互換性
    最初からDelegatingPasswordEncoderを使わなかったらどうなりますか?
    現在のDBの値にprefixがないため、クラスは対応できません.
    ただし、この場合に対応するため、defaultPasswordEncoderForMatchesを指定することができる.
    val encoder = DelegatingPasswordEncoder(
        "bcrypt",
        mapOf(
            "bcrypt" to BCryptPasswordEncoder(),
            "scrypt" to SCryptPasswordEncoder(),
            "pbkdf2" to Pbkdf2PasswordEncoder()
        )
    )
    
    encoder.setDefaultPasswordEncoderForMatches(BCryptPasswordEncoder())
    これにより,接頭辞のない暗号化ゲートが入ってくると,BCryptPasswordEncoderを用いて符号化を試みる.defaultPasswordEncoderForMatches指定されていない場合、接頭辞のないパスワードゲートが入るとEllegalArgumentExceptionが発生する
    Encodeのアップグレード
    インタフェースPasswordEncoderは、UpgradeEncodeによって、対応するパスワードをアップグレードする必要があるかどうかを判断します.
    org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder#upgradeEncoding
    @Override
    public boolean upgradeEncoding(String encodedPassword) {
    	if (encodedPassword == null || encodedPassword.length() == 0) {
    		this.logger.warn("Empty encoded password");
    		return false;
    	}
    	Matcher matcher = this.BCRYPT_PATTERN.matcher(encodedPassword);
    	if (!matcher.matches()) {
    		throw new IllegalArgumentException("Encoded password does not look like BCrypt: " + encodedPassword);
    	}
    	int strength = Integer.parseInt(matcher.group(2));
    	return strength < this.strength;
    }
    BCryptPasswordEncoderのソースコードを参照してください.
    BCCRyptPasswordEncoderは強度を指定し、この強度より低いパスワードを受信したときにtrueを返すことができます.
    DelegatingPasswordEncoder.upgradeEncoding()
    この方法では、DelegatingPasswordEncoderを使用する場合にも、次の操作を実行できます.
    @Compo
    class PasswordChecker(
        private val passwordUpdater: PasswordUpdater
    ) {
        private val encoder = DelegatingPasswordEncoder(
            "bcrypt",
            mapOf(
                "bcrypt" to BCryptPasswordEncoder(),
                "scrypt" to SCryptPasswordEncoder()
            )
        ).also {
            it.setDefaultPasswordEncoderForMatches(BCryptPasswordEncoder())
        }
    
        fun passwordConfirm(userId: Long, rawPassword: String, encryptedPassword: String): Boolean {
            if (!encoder.matches(rawPassword, encryptedPassword)) return false
            if (encoder.upgradeEncoding(encryptedPassword)) {
                val newPassword = encoder.encode(rawPassword)
                passwordUpdater.update(userId, newPassword)
            }
            return true 
        }
    }
    DelegatingPasswordEncoder.upgradeEncodeは、パスワード文がidForEncodeを使用していない場合(すなわち、ポーリングアルゴリズムを使用していない場合)にtrueを放出します.
    すなわち,上記コードの目的は,デバッガアルゴリズムを用いない暗号文をデバッガアルゴリズムのコードに変換することといえる.