일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- minreplica
- zset
- propogation
- 0 replica
- Database
- 스프링부트
- slow query
- logback
- docket
- blue-green
- yml
- Strimzi
- 동등성
- hammerDB
- Grafana
- Kafka
- Debezium
- traceId
- Salting
- keda
- Leaderboard
- Helm
- 소프트웨어 마에스트로
- SW 마에스트로
- spring boot
- Kubernetes
- SW Maestro
- Benchmarks
- Software maestro
- eks
- Today
- Total
김태오
salting, hashing 에 대한 이해와 주의점 본문
유저 정보 시스템을 구성할 때, 가장 중요한 요소는 유저 비밀번호의 보호이다.
이 때 사용하는 것이 salting 이라는 기법인데, 어원은 https://stackoverflow.com/questions/244903/why-is-a-password-salt-called-a-salt 라고한다.
딱히 그럴듯한 답은 없는데, foo - bar 등 흔히 쓰이는 용어들의 어원이 골때릴 때가 있다.
일단 해싱이라는 용어부터 짚고 가야 하는데, 요약하자면 문자열을 다른 문자열로 바꾸는 “단방향” 프로세스이다. 단방향이라 하면 해싱된 문자열을 원 문자열로 되돌릴 수 없음을 말한다.
반대로 양방향 암호화에는 key 등 암호화와 복호화 양쪽에서 공통으로 들고 있는 원 문자열과 바뀐 문자열을 유추할 수 있는 방법이 있다.
해싱에는 갖가지 알고리즘이 사용된다. Phpass, libsodium, sha256, sha512 등이 있는데 안전하고 공인된 알고리즘이 무엇인지는 찾아보도록 하자.
MD5, SHA1, SH2 등은 Merkle–Damgård construction 라는걸 사용하는데, 이건 length extension attack 에 취약하니 쓰지 말도록 하자.
우선 비밀번호를 사용한 인증 방식의 절차에 대해 알아보고자 한다.
- 유저는 계정을 생성한다.
- 비밀번호는 해싱 이후 DB 에 저장된다.
- 유저가 로그인하려 할 때, 유저가 입력한 비밀번호에 해싱 알고리즘을 사용하여 암호화한 뒤 DB 의 비밀번호와 대조한다.
- 해시가 일치할 경우 통과
대부분의 로그인 인증에서 4의 과정이 실패할 때 아이디가 틀렸는지, 비밀번호가 틀렸는지 알려주지 않는데, 이는 화나게 하려는 의도가 아닌 username 이 맞을 때의 password brute force 를 막기 위한 작은 보안 절차이다.
해싱 절차를 거친 보안이 안전할까? 당연히 그렇지 않다.
서비스에서 어떤 해싱 알고리즘을 사용하는지 파악이 되면, 유저 데이터베이스가 털린 후 password 까지 유추하는 과정이 어려울 리 없다.
만약 해싱 알고리즘을 알고 있지 않더라도, 해싱 알고리즘을 brute force 하는 방법으로 비밀번호를 알아낼 수도 있고, breacher 의 진심의 농도에 따라 lookup table, rainbow table 이라는걸 사용할 수도 있다.
요약하자면 해싱 알고리즘에 따라 해싱된 모든 문자열을 원 문자열로 되돌리는 key-value 형식의 테이블인데, 자연히 테이블 사이즈가 개크다.
복잡한 해싱 알고리즘들은 각각의 character 가 어떤 문자열로 치환되는 것이 아닌 character 들에 맞물려 있는 복잡한 형식의 암호화가 이루어지기에 “abcdef” 와 “abcdee” 의 암호화는 천지 차이가 나기 때문이다.
그래서 등장하는게 salt 이다.
Lookup table 과 rainbow table 의 동작 원리는 하나의 문자열이 정확히 다른 하나의 문자열로 치환되기 때문이다.
예를 들어 “password” 라는 비밀번호가 있다고 치자.
한 서비스에서 “password”를 암호화할 때, 아무리 복잡한 해싱 알고리즘을 쓴다 한들 모든 암호화된 “password” 들은 “password”에 대응된다. 이렇기에 해싱을 하기 전 “password” 에 특별한 문자열을 append 하여 그것을 해싱하는 것이며, 이 문자열을 salt 라 하는 것이다.
Salt 를 사용하는 것에도 여러 주의점이 있다.
우선 salt 를 재활용하는 것이다. Salt 의 uniqueness 를 해치는 순간 salt 를 문자열에 더하기 전 해싱의 보안 상태와 별 다를 부분이 없다. 공격자가 salt 가 무엇인지 파악이 된 후, 그저 salt 를 비밀번호에 append 하여 reverse lookup table 을 사용하면 된다.
Salt 는 각각의 유저 별로 반드시 달라야 한다. 이에는 언어에서 지원하는 random function 을 사용하여 각각의 character 를 생성할 수도 있지만, 그보다 안전한 CSPRNG 을 사용하자.
https://en.wikipedia.org/wiki/Cryptographically_secure_pseudorandom_number_generator 원리는 잘 모르겠지만 암호학적으로 더 안전한 랜덤 함수이다. CPRING 도 대부분의 언어별로 라이브러리가 존재한다. 예를 들어 Java 의 java.security.SecureRandom 과 Python 의 secrets
다음으로 작은 salt 문자열 크기이다. 예를 들어 salt 가 고작 4개의 ASCII 문자열을 사용한다면, 95^4 = 81450625 개의 salt 가 생성된다. 많아 보일지 몰라도, 현대 컴퓨터의 속도와 저장 공간의 용이성을 생각해 보았을 때 턱없이 부족한 숫자이다.
전문가들의 소견으로는 salt 가 더해진 후 해싱된 문자열 - 예컨데 SHA256 으로 해싱된 문자열 (256bits == 32bytes == 64 characters) 의 크기와 같거나 커야 된다고 한다. SHA512 를 사용하면 salt 의 최소 크기는 128 char 이라는 것이다.
이제 salt 가 붙은 문자열을 해싱할 차례이다.
SHA256, SHA512, WHIRLPOOL 등의 빠르고 안전한(안전하다고 여겨졌던) 알고리즘들이 존재하고, 이보다 조금 느리지만 현대 보안에 맞춰가는 PBKDF2, bcrypt, scrypt 등의 알고리즘이 있다.
오버엔지니어링일 수 있으니 서비스의 깜냥에 맞게 적절히 선택하자.
https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.22.0:scrypt/scrypt.go script 의 go library 소스코드인데, 개복잡한걸 보니 확실히 안전해 보인다.
다음은 python 으로 짠 salt 만들기에서 password hashing 까지의 짧은 함수이다. 역시 짧은 함수는? 파이썬
class EncryptPassword:
@staticmethod
def salt_password(password):
salt = os.urandom(64) # salt 생성
salt_hex = salt.hex() # 저장을 위한 salt > hex 로 변환
# PBKDF2 (SHA-512 의 100,000 반복) 으로 해싱
dk = hashlib.pbkdf2_hmac('sha512', password.encode(), salt, 100000, dklen=64)
hashed_password_hex = dk.hex() # 저장을 위한 비밀번호 > hex 로 변환
return hashed_password_hex, salt_hex