Hello World

[펌]spring security에서 sha256에서 bcrypt로 암호화 방식을 전환하는 방법 본문

Spring/Boot(4.x)

[펌]spring security에서 sha256에서 bcrypt로 암호화 방식을 전환하는 방법

EnterKey 2016. 1. 10. 13:00
반응형

애플리케이션 보안 측면에서 비밀번호를 암호화하는 것은 정말 중요하다. 비밀번호 암호화를 제대로 하지 않은 상태에서 서버가 털려 한 순간에 서비스가 위험에 처하는 경우를 볼 수 있다.

slipp.net은 자체 회원가입을 하면 sha256으로 비밀번호를 암호화한다. 자체 회원가입 기능을 적용할 때 비밀번호 암호화와 관련해 많은 고민 없이 적용했다. 그러다 학생 수업 준비하면서 안전한 패스워드 저장 문서를 읽은 후 bcrypt 암호화 방식을 적용해야겠다는 생각을 가지게 되었다.

그런데 문제는 기존에 sha256으로 암호화되어 있는 비밀번호도 유지하면서 자연스럽게 bcrypt 암호화 방식으로 전환해야되는데 좋은 방법이 생각나지 않아 무기한 연기하고 있었다. 그렇게 시간이 흘러 다시 한번 해결책을 찾다가 다음과 같은 방식으로 변경하기로 했다.

sha256과 bcrypt로 암호화하는 비밀번호를 같은 칼럼에서 유지하면서 비밀번호에 따라 다른 암호화 기술로 비교하는 것으로 결정했다. 이전에는 이 문제를 해결하기 위해 너무 어렵게 접근했는데 새롭게 적용한 접근방식은 자연스럽게 새로운 암호화 방식으로 전환할 수 있으며, 소스 코드 변경도 많지 않겠다는 생각이 든다.

이에 대한 아이디어는 Automatically converting password hashes in Grails spring-security-core 문서를 통해 얻었다.Automatically converting password hashes in Grails spring-security-core 문서는 spring security의 3.x에서 지원한 PasswordEncoder를 기반으로 하고 있다. 이와 같이 구현할 경우 기존의 new ShaPasswordEncoder(256)로 빈 설정되어 있는 부분을 new Sha256ToBCryptPasswordEncoder()로 빈 생성을 변경하면 다른 코드 수정없이 Sha256과 Bcrypt를 적용하는 것이 가능하다. 이것이 DI의 가장 큰 장점이지 않겠는가?

그런데 spring security에서 org.springframework.security.authentication.encoding.PasswordEncoder는 deprecated된 상태이다. 최신 버전에서는 org.springframework.security.crypto.password.PasswordEncoder을 사용할 것을 권장하고 있다. 최신 버전에서는 salt 값을 내부적으로 자동생성하는 방식으로 동작한다. 최신 버전의 PasswordEncoder에 대한 구현체를 다음과 같이 구현해 적용했다.

import org.springframework.security.authentication.encoding.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

public class Sha256ToBCryptPasswordEncoder implements PasswordEncoder {
    private PasswordEncoder bcryptPasswordEncoder;
    private MessageDigestPasswordEncoder sha256PasswordEncoder;
    private String salt;

    @Override
    public String encode(CharSequence rawPassword) {
        return bcryptPasswordEncoder.encode(rawPassword);
    }

    @Override
    public boolean matches(CharSequence rawPassword, String encodedPassword) {
        if (encodedPassword.startsWith("$2a$10$") && encodedPassword.length() == 60) {
            return bcryptPasswordEncoder.matches(rawPassword, encodedPassword);
        }

        if (encodedPassword.length() == 64) {
            return sha256PasswordEncoder.isPasswordValid(encodedPassword, rawPassword.toString(), salt;
        }

        return false;
    }

    public void setBcryptPasswordEncoder(PasswordEncoder bcryptPasswordEncoder) {
        this.bcryptPasswordEncoder = bcryptPasswordEncoder;
    }

    public void setSha256PasswordEncoder(MessageDigestPasswordEncoder sha256PasswordEncoder) {
        this.sha256PasswordEncoder = sha256PasswordEncoder;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

위와 같이 구현하고 적용하는데 2시간 정도 투자한 듯하다. 새로운 암호화 방식을 적용하는 작업이 큰 작업으로 생각했는데 막상 위와 같이 적용했더니 부담없이 적용할 수 있었다. 만약 PasswordEncoder의 interface가 변경되지 않았다면 더 적은 시간 투자로 변경할 수 있었겠다. 나도 암호화와 관련해 깊이 있는 지식을 가지고 있는 것은 아니지만 너무 두려워하지 말고 이미 나와 있는 암호화 라이브러리 잘 사용하고, 새롭게 등장하는 암호화 기술로 전환하는 방법에 대해 고민하면서 많은 비용을 투자하지 않고 적용할 수 있을 것이라 생각한다.

출처: https://slipp.net/questions/403

반응형
Comments