Spring Security PasswordEncoder

mainimg.jpg

Encoding, Encryption and Hashing

고객의 데이터를 저장할 때, 비밀번호와 같은 민감한 값은 Plaintext 형태로 직접 저장해서는 안된다. 데이터 보안의 핵심은 이러한 민감한 정보를 안전하게 보호하는 것이다. 이를 위해 흔히 사용하는 세 가지 방법은 Encoding, Encryption, 그리고 Hashing이다. 각 방법의 특징과 차이점을 이해하는 것은 매우 중요하다.

Encoding

  • 목적: 데이터를 다른 형식으로 변환하여 전송하거나 저장하는 과정.
  • 특징: 데이터의 변환은 가독성을 높이기 위한 것이며, 정보 자체의 보안을 고려하지 않는다.
  • 가역성: 가역적이다. 즉, 원래 데이터로 복원할 수 있다.
  • 예: Base64, URL Encoding 등.

Encryption

  • 목적: 데이터를 암호화하여 권한이 없는 사람이 읽을 수 없도록 보호.
  • 특징: 암호화 키를 사용하여 데이터를 변환하며, 해당 키를 사용하면 복호화가 가능하다.
  • 가역성: 가역적이다. 즉, 암호화된 데이터를 복호화하여 원래 데이터로 복원할 수 있다.
  • 사용 예: AES, RSA 등.

Hashing

  • 목적: 데이터를 고정된 길이의 해시 값으로 변환하여, 데이터 무결성 확인 및 비밀번호 저장 등 보안 용도로 사용.
  • 특징: 해시 함수는 동일한 입력에 대해 항상 동일한 출력을 생성하지만, 입력이 조금이라도 달라지면 완전히 다른 해시 값을 생성한다. 충돌(두 개의 다른 입력이 동일한 해시 값을 가지는 경우)을 최소화하도록 설계된다.
  • 가역성: 비가역적이다. 즉, 원래 데이터로 복원할 수 없다.
  • 사용 예: SHA-256, MD5 등.

Chosen one: Hashing

Spring Security와 같은 프레임워크에서 비밀번호를 저장할 때 Hashing을 사용하는 이유는 다음과 같다:

  1. 비가역성: 해싱된 비밀번호는 원래 비밀번호로 복원할 수 없으므로, 해커가 데이터베이스를 침투하더라도 해싱된 비밀번호를 원래 비밀번호로 변환할 수 없다. 이는 비밀번호가 유출되더라도 사용자의 원래 비밀번호를 보호하는 중요한 역할을 한다.
  2. 무결성 검증: 사용자가 로그인할 때 입력한 비밀번호를 해시 처리하여 저장된 해시 값과 비교하는 방식으로 무결성을 검증할 수 있다.
  3. 충돌 저항성: 해시 함수는 충돌을 최소화하도록 설계되어, 서로 다른 두 비밀번호가 동일한 해시 값을 갖는 경우가 매우 드물다. 이를 통해 데이터의 일관성을 유지할 수 있다.
  4. Salting: 해시 함수에 추가적으로 ‘salt’를 사용하면 동일한 비밀번호라도 다른 해시 값을 가지게 되어, 사전 공격(dictionary attack)이나 무차별 대입 공격(brute force attack)을 방지할 수 있다.

Encoding과 Encryption의 취약점

  • Encoding의 취약점:
    • 보안성 부족: 인코딩은 데이터를 읽기 쉽게 변환하는 것이 목적이기 때문에, 보안을 위한 수단으로 사용할 수 없다. 인코딩된 데이터는 누구나 쉽게 디코딩할 수 있다.
    • 무결성 미확보: 인코딩된 데이터는 변조될 수 있으며, 이를 식별할 수 있는 방법이 없다.
  • Encryption의 취약점:
    • 키 관리의 어려움: 암호화는 암호화 키와 복호화 키가 필요하기 때문에, 이 키를 안전하게 관리하는 것이 중요하다. 키가 유출되면 암호화된 데이터는 쉽게 복호화될 수 있다.
    • 복호화 가능성: 암호화된 데이터는 키를 이용해 언제든 복호화할 수 있으므로, 권한이 없는 사용자에게 키가 유출되면 데이터 보안이 위협받는다.

Hashing Algorithm

a1.png

해싱 알고리즘은 입력된 데이터를 고정된 크기의 해시 값으로 변환하는 수학적 함수이다. 해싱 알고리즘은 데이터의 무결성을 확인하거나 보안적인 목적으로 사용된다. 다음은 널리 사용되는 몇 가지 해싱 알고리즘이다:

  1. MD5 (Message Digest Algorithm 5): 128비트 해시 값을 생성하는 알고리즘. 현재는 충돌 가능성 때문에 보안 목적으로는 사용되지 않는다.
  2. SHA-1 (Secure Hash Algorithm 1): 160비트 해시 값을 생성하는 알고리즘. MD5보다 강력하지만, 현재는 충돌 가능성이 발견되어 안전하지 않다.
  3. SHA-256: 256비트 해시 값을 생성하는 알고리즘. 현재 보안성이 높다고 평가받아 널리 사용된다.
  4. BCrypt: 암호화된 해시 값을 생성하며, 내부적으로 salt를 사용하여 보안을 강화한 알고리즘. 비밀번호 해싱에 주로 사용된다.

회원가입 시 해싱된 비밀번호와 로그인 시 해싱된 비밀번호 비교 방법

사용자가 회원가입할 때와 로그인할 때, 비밀번호를 안전하게 비교하는 방법을 이해하기 위해서는 salting과 해싱 과정을 이해하는 것이 중요하다.

회원가입 시

  1. 사용자가 비밀번호를 입력한다.
  2. 시스템은 입력된 비밀번호에 고유한 salt 값을 추가한다. (salt는 무작위 데이터로, 동일한 비밀번호에 대해서도 매번 다른 해시 값을 생성하는 역할을 한다.)
  3. 비밀번호와 salt를 결합하여 해싱 알고리즘(Bcrypt 등)을 통해 해시 값을 생성한다.
  4. 생성된 해시 값과 salt를 데이터베이스에 저장한다.

사용자 비밀번호 + 고유한 Salt -> 해싱 -> 해시 값 (DB에 저장)

로그인 시

  1. 사용자가 비밀번호를 입력한다.
  2. 데이터베이스에서 해당 사용자의 salt 값을 가져온다.
  3. 입력된 비밀번호와 데이터베이스에서 가져온 salt를 결합한다.
  4. 결합된 값을 회원가입 시 사용한 동일한 해싱 알고리즘을 통해 해시 값을 생성한다.
  5. 생성된 해시 값과 데이터베이스에 저장된 해시 값을 비교하여 일치 여부를 확인한다.

사용자 비밀번호 + DB에서 가져온 Salt -> 해싱 -> 해시 값

Spring Security의 PasswordEncoder.matches() 동작 원리

Spring Security의 PasswordEncoder.matches() 메서드는 위의 과정을 내부적으로 처리하여 입력된 비밀번호가 저장된 해시 값과 일치하는지 확인한다.

  1. matches(rawPassword, encodedPassword) 메서드가 호출되면, rawPassword는 사용자가 로그인 시 입력한 비밀번호를 나타낸다.
  2. EncodedPassword는 데이터베이스에 저장된 해시 값을 나타낸다.
  3. EncodedPassword에는 실제 해시 값과 함께 salt 값이 포함되어 있다.
  4. matches() 메서드는 encodedPassword에서 salt를 추출하여 rawPassword와 결합한 후 해싱 알고리즘을 통해 새로운 해시 값을 생성한다.
  5. 생성된 해시 값과 encodedPassword의 해시 값을 비교하여 일치 여부를 반환한다.

이 과정을 통해 사용자가 입력한 비밀번호가 동일한지 확인할 수 있다. 비록 해싱된 비밀번호가 다를 수 있지만, salt 덕분에 동일한 비밀번호에 대해서는 항상 동일한 해시 값을 생성하여 비교할 수 있게 된다. 이는 해커가 사전 공격(dictionary attack)이나 무차별 대입 공격(brute force attack)을 통해 비밀번호를 추측하는 것을 어렵게 만든다.

BCryptPasswordEncoder

BCryptPasswordEncoder 설명

BCryptPasswordEncoder는 Spring Security에서 비밀번호를 해싱하고 검증하는 데 사용되는 클래스다. BCrypt 알고리즘은 내부적으로 고유한 salt 값을 생성하여 비밀번호와 결합한 후 해싱을 수행한다. 이렇게 생성된 해시 값에는 실제 해시 값뿐만 아니라 salt 값도 포함되어 있다. 이는 동일한 비밀번호가 여러 번 해싱될 때마다 다른 해시 값을 생성하게 함으로써 보안을 강화한다.

작동 원리

  1. 비밀번호 해싱:
    • 사용자가 비밀번호를 입력하면, BCrypt는 고유한 salt 값을 생성합니다.
    • 이 salt 값은 비밀번호와 결합된 후 해싱 알고리즘을 통해 해시 값이 생성됩니다.
    • 생성된 해시 값은 다음과 같은 형식으로 저장됩니다: $2a$$
    • 여기서 $2a$는 알고리즘 버전을 나타내고, 는 작업 인자, 는 생성된 salt 값, 는 비밀번호와 salt를 결합하여 생성된 해시 값입니다.
  2. 비밀번호 검증:
    • 사용자가 로그인할 때 입력한 비밀번호는 데이터베이스에 저장된 해시 값과 비교됩니다.
    • matches 메서드는 데이터베이스에 저장된 해시 값에서 salt 값을 추출하고, 입력된 비밀번호와 결합하여 새로운 해시 값을 생성합니다.
    • 생성된 해시 값이 데이터베이스에 저장된 해시 값과 일치하면, 비밀번호가 일치하는 것으로 간주됩니다.

SCryptPasswordEncoder & Argon2PasswordEncoder

SCryptPasswordEncoder 와 Argon2PasswordEncoder는 더 최신 해싱 알고리즘을 사용한다.

SCryptPasswordEncoder는 해싱을 위해 BCryptPasswordEncoder의 연산 능력 말고도 추가로 메모리를 더 요구한다. 따라서 해커가 Brute-Force 공격을 하려면 그에 따른 자원이 더 소모되게 될 것이다.

Argon2PasswordEncoder는 SCryptPasswordEncoder에 추가로 다중 쓰레드자원을 요구한다. 따라서 해커가 Brute-Force 공격을 하려면 엄청난 자원 소모를 감당해야 한다.

하지만 위 방법들은 서버의 연산능력도 그만큼 요구하기 때문에 일장일단이 있다.