Spring Security : 비밀번호 관리

송승훈·2024년 1월 12일

Spring Security

목록 보기
1/1

Background

비밀번호를 입력한 그대로 서버에 저장하게 되면 어떤 문제가 발생할까요? 일단 서버 관리자가 비밀번호를 알 수 있습니다. 이 경우 관리자가 마음대로 타 계정에 접근 할 수 있고, 이로 인해 개인정보가 유출 될 수 있습니다. 서버의 DB가 해킹되어 비밀번호가 유출될 경우에도 마찬가지 입니다.

이런 경우를 방지하기 비밀번호는 암호화해서 저장해야 합니다. 그리고 이를 위해 비밀번호를 알아볼 수 없게 바꾸는 작업에는 여러가지 방법이 있습입니다.

인코딩, 암호화, 해싱

어떠한 문자열의 원본을 알 수 없게 변경하는 데에는 크게 3가지 방법이 있습니다.

인코딩

인코딩이란 특정 알고리즘을 이용해 문자열을 변환하는 작업을 말합니다.
인코딩의 특징으로는
1. 누구나 다시 디코딩, 즉 원래 데이터로 되돌리는 것이 가능합니다.

암호화

암호화는 특정 키를 이용하여 데이터를 변환하는 작업을 말합니다.
암호화의 특징으로는
1. 암호화하는데 사용된 키가 있으면 복호화, 원래 데이터로 되돌리는 것이 가능합니다.

해싱

해싱이란 해시 함수를 이용해서 데이터를 고정된 길이의 데이터로 변환하는 작업을 말합니다.
해싱의 특징으로는
1. 입력값이 같으면 항상 같은 해시값을 반환합니다.
2. 단방향성, 즉 한번 해싱하면 값을 원본으로 되돌리는 것이 불가능합니다.

위의 3가지 방법들 중에서 비밀번호를 암호화하는데 적합한 방법은 해싱입니다.
인코딩은 누구나, 암호화는 키를 가진 자(예를들어 서버 관리자)가 암호화된 데이터를 되돌릴 수 있기 때문입니다.

따라서 암호화된 비밀번호를 다시 복구 할 수 없는 해싱이 비밀번호를 저장하는데에 가장 적합하다고 할 수 있습니다.

PasswordEncoder

Spring Security에서는 비밀번호를 해싱, 비교 작업을 추상화한 인터페이스로 PasswordEncoder를 제공합니다.

잠깐 구조를 살펴보자면

  • String encode(CharSequence rawPassword)
    • 비밀번호를 해시 알고리즘을 이용해 비밀번호를 해시값으로 변환하는 메서드입니다.
  • boolean matches(CharSequence rawPassword, String encodedPassword);
    • 입력된 비밀번호(해싱 전)해싱되어 저장된 비밀번호(해시값)를 비교하는 메서드입니다.
  • default boolean upgradeEncoding(String encodedPassword) { return false; }
    • 해싱된 비밀번호가 향상된 보안을 위해서 더 해시될 필요가 있는지를 판단하는 메서드입니다.

이제 PasswordEncoder가 하는 역할을 알았으니, 어디에서 사용되는지를 알아봅시다.
Spring Security에서 입력된 비밀번호와 저장된 해시값을 비교하는 작업은 어디서 이루어 질까요? 바로 AuthenticationProvider에서 이루어집니다.

DaoAuthenticationProvider에서 인증하기 위해 사용되는 모습

비밀번호 비교가 성공하면, 인증이 성공한 인증 객체 반환합니다.

PasswordEncoder는 인터페이스로써 자체를 사용할 수는 없습니다. Spring Security에서는 이를 구현한 다양한 구현체를 제공하는데 그것들의 일부을 살펴보겠습니다.

NoOpPasswordEncoder

비밀번호를 입력받은 그대로, 즉 아무런 해시를 적용하지 않는 구현체로 사용하신다면 테스트 단계에서만 사용하길 권장합니다.

StandardPasswordEncoder

SHA-256알고리즘을 사용한 구현체로, 이 또한 레거시 프로젝트를 지원하기 위한 구현체이므로 새 프로젝트에서는 사용하지 않는 것을 권장드립니다.

Pbkdf2PasswordEncoder

PBKDF2알고리즘을 사용한 구현체

NoOpPasswordEncoder제외한 위에 언급된 구현체들은 과거에는 안전하였지만
CPU, GPU가 발전하면서 연산능력이 향상된 지금은 무작위 대입 공격에 취약해졌습니다.

무작위 대입 공격이란 해싱된 비밀번호의 원본을 알아내기 위한 보안 공격기법으로
가장 자주 사용되는 비밀번호나, 무작위 문자열을 대입하여 비밀번호를 알아내는 공격 기법입니다.

해싱된 비밀번호가 유출 된 경우 무작위 대입 공격을 막을 수는 없습니다. 따라서 현재의 해싱 알고리즘들은 해시하는데에 비용이 많이 들도록하여, 해커들이 비밀번호를 알아내는데에 사용하는 비용을 크게 늘리는 방법을 사용하고 있습니다.

아래의 구현제들은 그러한 해시 알고리즘들을 사용한 구현체들이며, Spring Security에서 사용을 권장하는 구현체들입니다.

BCryptPasswordEncoder

BCrypt 알고리즘을 사용했으며
기본적으로 해싱하는데 연산비용이 다른 알고리즘들 보다 큽니다.

SCryptPasswordEncoder

BCryptPasswordEncoder의 향상된 버전으로
해싱할때 추가적인 메모리 할당을 요구하여, 연산하는데 비용을 더 늘린 방식

Argon2PasswordEncoder

SCryptPasswordEncoder의 향상된 버전으로
해싱할때 메모리 뿐만 아니라, 스레드까지 요구하므로써, 연산 비용을 더더욱 늘린 방식

설명만 보면 Argon2PasswordEncoder를 사용하는 것이 가장 좋아보이지만, 해시하는데 비용이 많이 든다는 것은 결국 서버에서 로그인 작업을 진행할때도 비용이 늘어난다는 이야기가 됩니다.

따라서 보안과 성능 등 여러가지를 고려해서 살펴보면, 현재로써는 굳이 SCryptPasswordEncoder까지도 갈 것 없이 BCryptPasswordEncoder를 사용하시는 것이 가장 권장됩니다.

BCryptPasswordEncoder

BCryptPasswordEncoder로 암호화된 비밀번호를 한번 살펴보겠습니다.

암호화된 비밀번호를 Parsing하기 위한 정규표현식
해싱된 비밀번호의 일부분

붉은색 밑줄인 $2a는 BCrypt 알고리즘의 버전을 의미합니다.

노란색 밑줄인 $10는 보안을 강화하기 위해 해시를 몇번이나 하였는지를 나타냅니다.
Spring Security에서는 Default 설정으로 10회를 하도록 설정되어 있습니다.

활용

이제 PasswordEncoder의 역할을 이해하고, 어디서 사용되는지도 알았으며, 어떤 구현체가 있는지도 알았습니다. 이를 활용하여 봅시다. 가장 먼저 사용할 구현체를 Bean으로 등록합니다.

회원가입

회원가입할때 입력받은 비밀번호는 저장할때 반드시 PasswordEncoder로 암호화한 후 저장해야 합니다.
따라서 회원가입을 진행하는 서비스에서 PasswordEncoder를 주입받고, 주입받은 PasswordEncoder로 .encode()메서드를 사용하여 비밀번호를 암호화 한 후에 저장소에 저장하면 됩니다.

인증

개인적으로 구현한 AuthenticationProvider를 사용하지 않는다면 PasswordEncoder를 Bean으로 등록만 해줘도 됩니다. Spring Security에서 제공하는 DaoAuthenticationProvider가 알아서 해당 Bean을 찾아서 사용하기 때문입니다.
커스텀 AuthenticationProvide는 경우는 추후 다루겠습니다.

0개의 댓글