우아한테크코스 테코톡

조로의 해커도 손절하는 안전한 비밀번호 저장 방법

https://youtu.be/sHqjW4l7GLU?si=l0y2iXd3Ma9FgAWV

조로의 해커도 손절하는 안전한 비밀번호 저장 방법


해커도 손절하는 안전한 비밀번호 저장 방법

비밀번호 저장은 인증 기능의 일부가 아니라, 시스템 전체 신뢰도를 결정하는 보안의 출발점이다. 실제 사례에서도 평문 계정 정보가 여러 서버에 남아 있으면, 한 번의 침투가 연쇄적인 내부 확장으로 이어질 수 있었다. 외부와 연결된 서버에서 시작된 침해가 다른 서버의 평문 계정 정보를 발판 삼아 코어망까지 이어졌고, 결국 주요 데이터 탈취로 연결됐다.

이 글에서는 왜 평문 저장이 위험한지, 왜 암호화만으로는 부족한지, 왜 단순 해싱도 안전하지 않은지, 그리고 최종적으로 어떤 방식으로 비밀번호를 저장해야 하는지를 순서대로 정리한다. 핵심 결론부터 말하면, 비밀번호는 복호화 가능한 방식으로 저장하면 안 되고, 솔트와 키 스트레칭이 적용된 전용 비밀번호 해시 알고리즘을 사용해야 한다.

왜 비밀번호 저장 방식이 중요한가

보안 사고는 흔히 “한 번에 모든 것이 털리는 사건”처럼 보이지만, 실제로는 작은 허점이 연결되면서 커진다. 자료에 나온 사례도 비슷하다. 외부 인터넷망과 연결된 서버 A를 침투한 뒤, 그 안에 평문으로 저장된 계정 정보로 서버 B에 들어가고, 다시 그 안의 평문 계정 정보로 더 깊은 내부 서버까지 접근하는 방식이었다. 즉, 공격자는 취약점 하나만 찾으면 되는 것이 아니라, 저장된 자격 증명 자체가 다음 공격 단계의 도구가 된다.

그래서 비밀번호 저장 방식은 단순히 “DB 유출 시 위험하다” 정도가 아니라, 내부 확산을 차단하는 기본 방어선으로 봐야 한다.

평문 저장은 왜 최악인가

평문 저장은 설명이 필요 없을 정도로 위험하다. DB가 유출되거나 서버 내부 파일이 노출되는 순간, 공격자는 추가 작업 없이 바로 계정을 사용할 수 있다. 더 심각한 문제는 사용자들이 여러 서비스에서 같은 비밀번호를 재사용하는 경우가 많다는 점이다. 한 서비스의 평문 비밀번호 유출이 다른 서비스 계정 탈취로까지 이어질 수 있다.

즉, 평문 저장은 “비밀번호를 보호하지 않았다”가 아니라 “비밀번호를 그대로 넘겨줬다”에 가깝다. 자료에서도 이 점 때문에 프로젝트를 할 때 지금부터 계정 보안에 신경 써야 한다고 강조한다.

암호화해서 저장하면 되지 않을까

평문이 위험하니, 가장 먼저 떠오르는 대안은 암호화다. 비밀번호를 어떤 키로 암호화해 DB에 저장하면, 겉보기에는 평문보다 안전해 보인다.

하지만 비밀번호 저장 용도로는 이 방식이 충분히 안전하지 않다. 이유는 간단하다. 암호화는 양방향 프로세스이기 때문이다. 암호화 키로 변환한 값은 대응되는 복호화 키가 있으면 다시 원문으로 되돌릴 수 있다. 결국 공격자가 암호문만 가져가는 것이 아니라, 서버나 설정 어딘가에 저장된 키까지 탈취하면 원래 비밀번호를 복원할 수 있다. 자료도 바로 이 점 때문에 “비밀번호를 암호화해서 저장하는 것은 안전하지 않다”고 설명한다.

비밀번호 저장은 본질적으로 “원문을 다시 알아낼 필요가 없는 데이터”다. 로그인 시 필요한 것은 원래 비밀번호를 복호화하는 것이 아니라, 사용자가 입력한 비밀번호가 저장된 값과 일치하는지 검증하는 일이다. 이 특성 때문에 비밀번호 저장에는 양방향 암호화보다 단방향 방식이 맞다.

그래서 해싱이 등장한다

암호화의 한계를 피하려면 단방향 프로세스가 필요하다. 그 대표가 해싱이다. 해싱은 입력값을 고정 길이의 다이제스트로 바꾸고, 그 결과를 원래 값으로 복원할 수 없게 만든다. 자료에서는 이 차이를 직관적으로 설명하기 위해 암호화를 “레고를 분해했다가 다시 조립할 수 있는 것”, 해싱을 “레고를 불태워 원래 상태로 되돌릴 수 없는 것”에 비유한다.

비밀번호 저장에서 해싱의 장점은 분명하다. DB가 유출되어도 저장된 값이 곧바로 원래 비밀번호를 알려주지 않는다. 로그인 시에는 사용자가 입력한 비밀번호를 같은 방식으로 다시 해싱한 뒤, 저장된 해시와 비교하면 된다.

여기까지만 보면 해싱이 정답처럼 보인다. 하지만 문제가 하나 더 있다.

단순 해싱도 안전하지 않은 이유

해싱에는 중요한 특징이 하나 있다. 같은 값을 같은 해시 함수에 넣으면 항상 같은 결과가 나온다. 이 특성 덕분에 로그인 검증이 가능하지만, 반대로 공격자도 같은 이점을 얻는다. 예를 들어 공격자가 qwer를 SHA-256으로 해싱한 결과를 알고 있다면, DB에서 같은 다이제스트를 발견했을 때 “이 값은 qwer를 SHA-256으로 해싱한 결과구나”라고 추론할 수 있다.

이 원리를 체계화한 것이 레인보우 테이블 공격이다. 레인보우 테이블은 원본 값과 해시 값의 대응 관계를 미리 계산해 저장해 둔 사전이다. DB 유출 후 공격자는 해시를 복호화하는 것이 아니라, 이미 계산된 테이블에서 대응되는 원문을 찾아낸다. 자료에서도 특정 다이제스트를 사이트에 넣으면 원본과 해시 방식이 쉽게 드러나는 예를 보여준다.

즉, 단순 해싱은 “복호화는 안 되지만 추측은 가능하다”는 한계를 가진다.

솔트가 왜 필요한가

이 문제를 해결하기 위해 등장한 것이 솔트다. 솔트는 비밀번호에 랜덤한 값을 더한 뒤 해싱하는 방식이다. 같은 비밀번호라도 사용자마다 다른 솔트를 붙이면 전혀 다른 해시 값이 나온다. 자료에서는 이를 해싱의 눈사태 효과로 설명한다. 입력값이 조금만 바뀌어도 출력값이 크게 바뀌기 때문에, 기존에 만들어 둔 레인보우 테이블은 더 이상 그대로 사용할 수 없게 된다.

솔트의 핵심 가치는 두 가지다.

첫째, 같은 비밀번호를 쓰는 사용자라도 저장된 해시가 서로 달라진다. 둘째, 공격자가 레인보우 테이블을 재사용하기 어려워진다. 각 솔트마다 별도 계산이 필요하기 때문이다.

즉, 솔트는 해싱을 “사전 공격에 취약한 고정 변환”에서 “사용자별로 독립적인 변환”으로 바꿔준다.

그런데 솔트만으로도 충분하지 않다

솔트가 레인보우 테이블을 무력화해도, 여전히 공격자는 무차별 대입을 할 수 있다. 하드웨어가 발전하면서 해시 계산 속도도 계속 빨라졌기 때문이다. 자료에서도 연산 성능 향상이 보안 관점에서는 공격 속도 향상으로 이어진다고 설명한다. 예시로 SHA-1 해싱 충돌 공격이 GTX 1080 기준 8년이면 가능하다는 자료를 언급하면서, 더 최신 하드웨어라면 공격 속도는 더 빨라질 수 있다고 지적한다.

핵심은 이것이다. 해싱이 너무 빠르면, 공격자도 엄청나게 빠르게 후보 비밀번호를 시험해볼 수 있다. 비밀번호 저장용 알고리즘은 오히려 “빠른 것”이 장점이 아니라 약점이 될 수 있다.

키 스트레칭이 필요한 이유

그래서 등장한 개념이 키 스트레칭이다. 키 스트레칭은 해싱을 반복하거나, 느린 연산을 반복해서 의도적으로 계산 비용을 높이는 방식이다. 정상 사용자는 로그인할 때 한 번 계산하면 되지만, 공격자는 수많은 후보 비밀번호를 시도해야 하므로 비용 증가가 훨씬 크게 작용한다. 자료도 키 스트레칭의 목적을 “연산 속도를 느리게 만들어 공격 속도도 같이 늦추는 것”으로 설명한다.

또한 반복 횟수 같은 파라미터를 통해 연산 강도를 유연하게 조절할 수 있다는 점도 중요하다. 하드웨어 성능이 높아지면 비용 인자를 조정해 방어 수준을 올릴 수 있다. 즉, 키 스트레칭은 고정된 방어가 아니라 시간에 따라 튜닝 가능한 방어다.

결국 안전한 비밀번호 저장의 핵심 공식

여기까지 정리하면 안전한 비밀번호 저장은 다음 흐름으로 요약된다.

평문 저장은 안 된다. 암호화 저장도 안 된다. 단순 해싱도 안 된다. 랜덤 솔트를 사용하고, 계산 비용을 높이는 키 스트레칭이 포함된 전용 비밀번호 해시 알고리즘을 써야 한다.

이 요구사항을 충족하도록 설계된 대표 알고리즘이 BCrypt, SCrypt, Argon2다.

BCrypt는 무엇인가

BCrypt는 Blowfish 기반으로 만들어진 알고리즘이며, 복잡한 비트 연산을 반복해 연산 강도를 조절한다. 자료에서는 스프링 표준이 BCrypt라고 설명하고, 아직 취약점이 발견되지 않았으며 관련 자료도 많아 보안성과 개발 편의성 측면을 함께 고려하면 좋은 선택이라고 말한다.

실무적으로 BCrypt의 장점은 분명하다. 라이브러리 지원이 풍부하고, 프레임워크 통합이 쉽고, 운영 경험도 많다. 그래서 기존 스프링 기반 서비스나 빠르게 안정적인 기본값이 필요한 환경에서는 매우 현실적인 선택이다.

SCrypt는 무엇이 더 강한가

SCrypt는 BCrypt보다 더 강력한 방향으로 발전한 알고리즘으로 소개된다. 핵심은 메모리 하드(memory-hard) 특성이다. 단순히 CPU 연산만 많이 시키는 것이 아니라, 큰 메모리를 할당하고 순차적으로 접근하게 만들어 GPU 같은 병렬 처리 장비가 효율적으로 공격하기 어렵게 한다. 자료에서도 대규모 메모리 접근으로 인해 GPU 병목이 생기고, 그 덕분에 GPU 기반 공격 방어가 가능하다고 설명한다.

즉, SCrypt는 “느리게 만들자”를 넘어서 “병렬 공격 장비가 강점을 발휘하기 어렵게 만들자”까지 확장한 방식이다.

Argon2가 왜 많이 언급되는가

Argon2는 현재 가장 강력한 선택지로 자주 평가된다. 자료에서는 SCrypt가 순차적으로 메모리에 접근하는 것과 달리, Argon2는 무작위 또는 데이터 종속적으로 메모리에 접근해 SCrypt보다 더 강하게 GPU 방어를 수행한다고 설명한다.

실무에서 Argon2가 주목받는 이유는 단순히 “최신이라서”가 아니라, 현대적인 공격 모델, 특히 병렬 하드웨어 공격에 대한 대응을 더 잘 고려했기 때문이다. 새 프로젝트에서 장기적인 보안성을 더 강하게 가져가고 싶다면 Argon2가 유력한 후보가 되는 이유가 여기에 있다.

그래서 무엇을 써야 할까

자료의 결론은 꽤 균형적이다. 스프링 생태계와 개발 편의성까지 고려하면 BCrypt는 여전히 매우 좋은 선택이다. 반면, 더 보안 중심적인 최신 권고를 보면 새 프로젝트에서는 Argon2를 먼저 고려하고, 차선책으로 SCrypt를 검토하라는 흐름도 많다. 결국 답은 하나로 고정되지 않고, 프로젝트 성격과 기술 스택, 운영 편의성, 장기 보안 요구사항을 함께 보고 선택해야 한다는 것이다.

실무적으로 정리하면 이렇게 볼 수 있다.

스프링 기반 서비스에서 빠르고 안정적인 선택이 필요하다면 BCrypt는 충분히 강력하고 현실적이다. 장기 운영과 더 높은 보안 요구사항을 갖는 신규 프로젝트라면 Argon2를 먼저 검토할 만하다. Argon2 도입 여건이 애매하다면 SCrypt도 좋은 대안이 된다.

구현할 때 꼭 기억해야 할 포인트

비밀번호 저장에서 중요한 것은 알고리즘 이름만 외우는 것이 아니다. 실제 구현에서도 몇 가지 원칙을 지켜야 한다.

첫째, 비밀번호는 절대 평문으로 로그에 남기지 말아야 한다. 둘째, 직접 SHA-256 같은 일반 해시 함수를 조합해서 구현하기보다, 검증된 비밀번호 해시 라이브러리를 써야 한다. 셋째, 솔트는 사용자별로 달라야 하고, 전용 알고리즘이 내부적으로 관리하게 두는 것이 안전하다. 넷째, 비용 인자도 기본값에만 의존하지 말고, 서비스 응답 속도와 서버 성능을 고려해 적절히 조정해야 한다.

이 원칙을 지키지 않으면 “알고리즘은 맞게 골랐는데 실제 보안 수준은 기대보다 낮은” 상태가 되기 쉽다.

마무리

비밀번호 저장은 보안의 부가 기능이 아니라 기본기다. 평문 저장은 말할 것도 없고, 암호화 저장도 키 탈취라는 구조적 위험이 있다. 단순 해싱은 레인보우 테이블 같은 사전 공격에 취약하다. 그래서 안전한 방식은 결국 솔트를 적용하고, 키 스트레칭을 포함한 전용 비밀번호 해시 알고리즘을 사용하는 것으로 정리된다.

가장 중요한 관점은 이것이다. 비밀번호 저장 방식은 “DB가 털리지 않으면 괜찮다”가 아니라, DB가 털려도 최대한 버티게 만드는 설계여야 한다. 그 기준에서 보면, 오늘날 비밀번호 저장의 실무적인 답은 BCrypt, SCrypt, Argon2 같은 알고리즘을 올바르게 사용하는 것이다. 그리고 새 프로젝트일수록 Argon2까지 적극적으로 검토할 가치가 있다.


© 2020. All rights reserved.

SIKSIK