
고객의 데이터를 저장할 때, 비밀번호와 같은 민감한 값은 Plaintext 형태로 직접 저장해서는 안된다. 데이터 보안의 핵심은 이러한 민감한 정보를 안전하게 보호하는 것이다. 이를 위해 흔히 사용하는 세 가지 방법은 Encoding, Encryption, 그리고 Hashing이다. 각 방법의 특징과 차이점을 이해하는 것은 매우 중요하다.
Encoding
- 목적: 데이터를 다른 형식으로 변환하여 전송하거나 저장하는 과정.
- 특징: 데이터의 변환은 가독성을 높이기 위한 것이며, 정보 자체의 보안을 고려하지 않는다.
- 가역성: 가역적이다. 즉, 원래 데이터로 복원할 수 있다.
- 예: Base64, URL Encoding 등.
Encryption
- 목적: 데이터를 암호화하여 권한이 없는 사람이 읽을 수 없도록 보호.
- 특징: 암호화 키를 사용하여 데이터를 변환하며, 해당 키를 사용하면 복호화가 가능하다.
- 가역성: 가역적이다. 즉, 암호화된 데이터를 복호화하여 원래 데이터로 복원할 수 있다.
- 사용 예: AES, RSA 등.
Hashing
- 목적: 데이터를 고정된 길이의 해시 값으로 변환하여, 데이터 무결성 확인 및 비밀번호 저장 등 보안 용도로 사용.
- 특징: 해시 함수는 동일한 입력에 대해 항상 동일한 출력을 생성하지만, 입력이 조금이라도 달라지면 완전히 다른 해시 값을 생성한다. 충돌(두 개의 다른 입력이 동일한 해시 값을 가지는 경우)을 최소화하도록 설계된다.
- 가역성: 비가역적이다. 즉, 원래 데이터로 복원할 수 없다.
- 사용 예: SHA-256, MD5 등.
Spring Security와 같은 프레임워크에서 비밀번호를 저장할 때 Hashing을 사용하는 이유는 다음과 같다:
- 비가역성: 해싱된 비밀번호는 원래 비밀번호로 복원할 수 없으므로, 해커가 데이터베이스를 침투하더라도 해싱된 비밀번호를 원래 비밀번호로 변환할 수 없다. 이는 비밀번호가 유출되더라도 사용자의 원래 비밀번호를 보호하는 중요한 역할을 한다.
- 무결성 검증: 사용자가 로그인할 때 입력한 비밀번호를 해시 처리하여 저장된 해시 값과 비교하는 방식으로 무결성을 검증할 수 있다.
- 충돌 저항성: 해시 함수는 충돌을 최소화하도록 설계되어, 서로 다른 두 비밀번호가 동일한 해시 값을 갖는 경우가 매우 드물다. 이를 통해 데이터의 일관성을 유지할 수 있다.
- Salting: 해시 함수에 추가적으로 ‘salt’를 사용하면 동일한 비밀번호라도 다른 해시 값을 가지게 되어, 사전 공격(dictionary attack)이나 무차별 대입 공격(brute force attack)을 방지할 수 있다.
Encoding과 Encryption의 취약점
- Encoding의 취약점:
- 보안성 부족: 인코딩은 데이터를 읽기 쉽게 변환하는 것이 목적이기 때문에, 보안을 위한 수단으로 사용할 수 없다. 인코딩된 데이터는 누구나 쉽게 디코딩할 수 있다.
- 무결성 미확보: 인코딩된 데이터는 변조될 수 있으며, 이를 식별할 수 있는 방법이 없다.
- Encryption의 취약점:
- 키 관리의 어려움: 암호화는 암호화 키와 복호화 키가 필요하기 때문에, 이 키를 안전하게 관리하는 것이 중요하다. 키가 유출되면 암호화된 데이터는 쉽게 복호화될 수 있다.
- 복호화 가능성: 암호화된 데이터는 키를 이용해 언제든 복호화할 수 있으므로, 권한이 없는 사용자에게 키가 유출되면 데이터 보안이 위협받는다.

해싱 알고리즘은 입력된 데이터를 고정된 크기의 해시 값으로 변환하는 수학적 함수이다. 해싱 알고리즘은 데이터의 무결성을 확인하거나 보안적인 목적으로 사용된다. 다음은 널리 사용되는 몇 가지 해싱 알고리즘이다:
- MD5 (Message Digest Algorithm 5): 128비트 해시 값을 생성하는 알고리즘. 현재는 충돌 가능성 때문에 보안 목적으로는 사용되지 않는다.
- SHA-1 (Secure Hash Algorithm 1): 160비트 해시 값을 생성하는 알고리즘. MD5보다 강력하지만, 현재는 충돌 가능성이 발견되어 안전하지 않다.
- SHA-256: 256비트 해시 값을 생성하는 알고리즘. 현재 보안성이 높다고 평가받아 널리 사용된다.
- BCrypt: 암호화된 해시 값을 생성하며, 내부적으로 salt를 사용하여 보안을 강화한 알고리즘. 비밀번호 해싱에 주로 사용된다.
회원가입 시 해싱된 비밀번호와 로그인 시 해싱된 비밀번호 비교 방법
사용자가 회원가입할 때와 로그인할 때, 비밀번호를 안전하게 비교하는 방법을 이해하기 위해서는 salting과 해싱 과정을 이해하는 것이 중요하다.
회원가입 시
- 사용자가 비밀번호를 입력한다.
- 시스템은 입력된 비밀번호에 고유한 salt 값을 추가한다. (salt는 무작위 데이터로, 동일한 비밀번호에 대해서도 매번 다른 해시 값을 생성하는 역할을 한다.)
- 비밀번호와 salt를 결합하여 해싱 알고리즘(Bcrypt 등)을 통해 해시 값을 생성한다.
- 생성된 해시 값과 salt를 데이터베이스에 저장한다.
사용자 비밀번호 + 고유한 Salt -> 해싱 -> 해시 값 (DB에 저장)
로그인 시
- 사용자가 비밀번호를 입력한다.
- 데이터베이스에서 해당 사용자의 salt 값을 가져온다.
- 입력된 비밀번호와 데이터베이스에서 가져온 salt를 결합한다.
- 결합된 값을 회원가입 시 사용한 동일한 해싱 알고리즘을 통해 해시 값을 생성한다.
- 생성된 해시 값과 데이터베이스에 저장된 해시 값을 비교하여 일치 여부를 확인한다.
사용자 비밀번호 + DB에서 가져온 Salt -> 해싱 -> 해시 값
Spring Security의 PasswordEncoder.matches() 동작 원리
Spring Security의 PasswordEncoder.matches() 메서드는 위의 과정을 내부적으로 처리하여 입력된 비밀번호가 저장된 해시 값과 일치하는지 확인한다.
- matches(rawPassword, encodedPassword) 메서드가 호출되면, rawPassword는 사용자가 로그인 시 입력한 비밀번호를 나타낸다.
- EncodedPassword는 데이터베이스에 저장된 해시 값을 나타낸다.
- EncodedPassword에는 실제 해시 값과 함께 salt 값이 포함되어 있다.
- matches() 메서드는 encodedPassword에서 salt를 추출하여 rawPassword와 결합한 후 해싱 알고리즘을 통해 새로운 해시 값을 생성한다.
- 생성된 해시 값과 encodedPassword의 해시 값을 비교하여 일치 여부를 반환한다.
이 과정을 통해 사용자가 입력한 비밀번호가 동일한지 확인할 수 있다.
비록 해싱된 비밀번호가 다를 수 있지만, salt 덕분에 동일한 비밀번호에 대해서는 항상 동일한 해시 값을 생성하여 비교할 수 있게 된다.
이는 해커가 사전 공격(dictionary attack)이나 무차별 대입 공격(brute force attack)을 통해 비밀번호를 추측하는 것을 어렵게 만든다.
BCryptPasswordEncoder 설명
BCryptPasswordEncoder는 Spring Security에서 비밀번호를 해싱하고 검증하는 데 사용되는 클래스다. BCrypt 알고리즘은 내부적으로 고유한 salt 값을 생성하여 비밀번호와 결합한 후 해싱을 수행한다. 이렇게 생성된 해시 값에는 실제 해시 값뿐만 아니라 salt 값도 포함되어 있다. 이는 동일한 비밀번호가 여러 번 해싱될 때마다 다른 해시 값을 생성하게 함으로써 보안을 강화한다.
작동 원리
- 비밀번호 해싱:
- 사용자가 비밀번호를 입력하면, BCrypt는 고유한 salt 값을 생성합니다.
- 이 salt 값은 비밀번호와 결합된 후 해싱 알고리즘을 통해 해시 값이 생성됩니다.
- 생성된 해시 값은 다음과 같은 형식으로 저장됩니다: $2a$$
- 여기서 $2a$는 알고리즘 버전을 나타내고, 는 작업 인자, 는 생성된 salt 값, 는 비밀번호와 salt를 결합하여 생성된 해시 값입니다.
- 비밀번호 검증:
- 사용자가 로그인할 때 입력한 비밀번호는 데이터베이스에 저장된 해시 값과 비교됩니다.
- matches 메서드는 데이터베이스에 저장된 해시 값에서 salt 값을 추출하고, 입력된 비밀번호와 결합하여 새로운 해시 값을 생성합니다.
- 생성된 해시 값이 데이터베이스에 저장된 해시 값과 일치하면, 비밀번호가 일치하는 것으로 간주됩니다.
SCryptPasswordEncoder 와 Argon2PasswordEncoder는 더 최신 해싱 알고리즘을 사용한다.
SCryptPasswordEncoder는 해싱을 위해 BCryptPasswordEncoder의 연산 능력 말고도 추가로 메모리를 더 요구한다. 따라서 해커가 Brute-Force 공격을 하려면 그에 따른 자원이 더 소모되게 될 것이다.
Argon2PasswordEncoder는 SCryptPasswordEncoder에 추가로 다중 쓰레드자원을 요구한다. 따라서 해커가 Brute-Force 공격을 하려면 엄청난 자원 소모를 감당해야 한다.
하지만 위 방법들은 서버의 연산능력도 그만큼 요구하기 때문에 일장일단이 있다.