비밀번호 재사용 방지, 현대적인 단방향 암호화의 성능 이슈 (Restricted Password Reuse; Password Reuse Prevention)
글의 요약 (주요 글감)
최근 사용한 비밀번호 100개를 재사용 할 수 없도록 막는 데에 기존 현대적인 단방향 암호화로는 큰 시간을 소요함. (해커가 동시에 여러 비밀번호를 시도하기 더 번거롭도록 설계됨.)
인증용 암호는 BCrypt, SCrypt, Argon2 등 충분히 안전한 암호화 함수와 가변 솔트 사용
비밀번호 재사용 확인용 해시는 암호학적 해시 함수, 사용자 고정 솔트, 페퍼또는 시크릿 키를 사용
이 방식의 취약점 대처 1 요약: DB의 해시 값 유출 시
DB의 해시 값 유출 시, 비밀번호 재사용 확인용 해시는 인증용 암호에 비해 쉽게 해독될 수 있음.
- 이는 사용자의 인증용 암호를 유추하는 새로운 수단이 됨.
(인증 테이블 대신 히스토리 테이블을 공략할 수 있음.)
- 이는 사용자의 인증용 암호를 유추하는 새로운 수단이 됨.
사용자별 고정 솔트를 인증 DB와 격리된 공간에 저장하면 DB 유출 시 해독에 대한 면역성 증가
격리된 저장소를 운영하기 어렵다면, 복잡하게 연산하여 솔트를 구하도록 해도 어느 정도 보호됨.
이 방식의 취약점 대처 2 요약: 현재 사용 중인 비밀번호의 쉬운 해시가 생성되지 않게 하기
새 비밀번호(인증에 사용하는 비밀번호)는 히스토리를 저장하지 않음.
그 대신 비밀번호 변경 시 기존 비밀번호를 함께 받고, 기존 비밀번호로 인증에 성공하면, 만료되는 기존 비밀번호를 해싱하여 히스토리에 저장하는 방식을 택할 수 있음.
비밀번호 히스토리의 모든 해시 값이 노출되어도 '지금 사용 중인 비밀번호'의 해시 값은 없음.
인증용 테이블에 있는 비밀번호 해시 값은 가변 솔트를 사용하여 저장되는 방식을 유지함.
(현재 사용 중인 비밀번호는 이 테이블에만 존재)
대략적인 권장의 예시 1
비밀번호 변경 요청에서 기존 비밀번호와 새 비밀번호를 모두 받음.
인증용 암호화에는 Argon2id 사용을 권장할 수 있음.
히스토리용 암호화는 Argon2d 사용을 권장할 수 있으며, 이는 인증에 사용되어선 안 됨.
1단계: 기존 비밀번호 인증(Argon2id)
2단계: 히스토리 검토(Argon2d 및 고정 솔트)
* Argon2 함수는 가변솔트가 표준이므로, 비표준적인 사용이며 일부분 직접 구현해야 할 수 있음.인증에 성공해야 이 단계를 수행하므로 사이드 채널 어택 가능성이 거의 없음.
(반드시 Argond2i나 Argon2id를 택하지 않아도 됨.)해시 값 유출에 대한 대비를 더 강화하는 것이 좋음.
(해시 값 유출에 한하여 Argon2d가 Argon2id보다 저항성이 있음.)
3단계: 암호화한 해시 저장
새 비밀번호는 암호화하여 인증 테이블에 저장(Argon2id) - 이 단계에 사용자에게 응답
기존 비밀번호는 비밀번호 히스토리에 저장(Argon2d) - 이 작업은 비동기로 넘겨 두어도 됨.
대략적인 권장의 예시 2: 재인증과 토큰을 통한 인가 (사용자 경험 개선)
요청 1: 비밀번호 변경 전 재인증 수행
기존 비밀번호 입력으로 인증하고 토큰 발행 (이때 응답)
이때 토큰은 JWT처럼 원문과 함께 보내는 무결성 체크가 아님.
기존 비밀번호는 히스토리용으로 암호화하여 미리 임시 보관을 할 수 있음.
(비동기로 처리할 수 있으므로 인증 시간을 불필요하게 늘리지 않을 수 있음.)
요청 2: 토큰으로 인가하여 비밀번호 변경
인가 과정이 토큰으로 변경되었기 때문에 충분히 빠름.
새 비밀번호를 히스토리용으로 암호화하여 히스토리 테이블과 비교
히스토리에 중복이 없다면 새 비밀번호를 인증용으로 암호화하여 인증 테이블에 저장 및 응답
임시 보관되어 있던 기존 비밀번호의 히스토리용 암호화 해시 값을 히스토리 테이블에 저장
배경
너무 잦은 비밀번호 변경은 비밀번호 재사용을 유발
암호학과 사회공학의 결합, NIST의 가이드라인 변화
"비밀번호를 자주 바꾸게 하면, 오히려 쉬운 암호를 찾거나 과거에 사용했던 암호를 (비슷하게나 그대로) 다시 사용합니다."
과거부터 암호학은 연산중심적으로 비밀번호의 안전성을 평가하면서, 동시에 사용자들의 비밀번호 사용 습관이나 생활 습관을 반영하면서 발전해 온 것 같습니다. 그러다가 사람들의 습관에 따라 가이드라인을 완전히 바꾸어 놓기도 하고, 일부 규칙은 사용자가 생각보다 더 '스스로를 보호하는 습관을 만들지 않을 것이기 때문'에 바뀌었죠. 그중 하나가 비밀번호 업데이트 주기입니다.
비밀번호 업데이트 시점
요즘 NIST 비밀번호 가이드라인은 비밀번호의 잦은 업데이트를 추천하지 않습니다. NIST가 권장하는 비밀번호 업데이트 시점은 다음과 같죠.
❌ 90일에 한 번씩 변경하도록 권장합니다. (옛날)
❌ 1년에 한 번 정도는 바꾸어야 좋습니다. (루머)
✅ 비밀번호가 유출되었다는 것이 발견될 때는 바꾸어야 합니다.
✅ 사용자가 원하면 바꿀 수 있습니다.
비밀번호를 자주 바꾸는 것은 그다지 도움이 되는 보안이 아니었으며, 오히려 쉬운 암호 사용이나 과거에 사용했던 암호를 다시 사용하는 습관을 유발했죠.
https://www.auditboard.com/blog/nist-password-guidelines/
<디지털 신원 가이드라인>, NIST SP-800-63, 2020
비밀번호 길이(복잡성 요구사항보다), 저장된 비밀번호의 솔팅 및 해싱, MFA(다중 인증: 2FA 등), 사용자가 비밀번호 보안 정책을 더 쉽게 준수할 수 있도록 안내
조직은 직원들에게 1년에 한 번 이상 비밀번호를 재설정 하도록 요구해서는 안 되며, 새 비밀번호를 모니터링 하여 일반적인 비밀번호 및 유출된 비밀번호 목록과 비교하여 테스트할 것.
과거에는 NIST가 오히려 비밀번호를 자주 바꾸기를 권했고(90일), 그것이 전세계적으로 '상식'이 되어, 우리나라에서는 개인정보를 다루는 직원들의 계정은 6개월에 한 번씩 반드시 비밀번호를 바꾸어야 하죠. 그리고 아직 많은 사이트에서 일반 사용자 계정에도 비밀번호를 바꿀 건지 3개월마다 묻습니다. 그것이 세계적인 상식에서 원래 보안에 좋은 거였으니까요. 이런 습관은 시나브로 바뀌어 갈 것 같습니다.
과거 비밀번호의 재사용 방지하기
"비밀번호를 자주 바꾸지 않게 하는 것도, 쉬운 비밀번호 사용과 비밀번호 재사용을 막기 위해서입니다."
과거에 사용한 비밀번호는 보안에 비교적 취약하기 때문입니다.
비밀번호에 관련한 공격은 주로 사람들의 생활 습관을 반영합니다. 많은 사람들은 과거에 사용한 적 있는 비밀번호를 다시 사용하거나, 여러 사이트에서 동일한 비밀번호를 사용하는 습관을 갖고 있죠. 공격자는 사용자가 비슷하거나 동일한 비밀번호를 반복적으로 사용할 가능성을 고려할 수 있고, 아마 공격 전략에 사용자의 유출된 적 있는(다른 사이트를 통해서라도) 비밀번호나 그와 비슷한 비밀번호를 포함할 겁니다.
사용자는 비밀번호의 재사용과 관련해서 다음 세 가지를 조심해야 합니다.
여러 서비스에 동일한 비밀번호를 사용하지 않습니다.
이미 유출된 것으로 잘 알려진 비밀번호를 다시 사용하지 않습니다.
사용한 적이 있는 비밀번호와 동일하거나 비슷한 비밀번호를 사용하지 않습니다.
1, 2, 3 중에서 1번은 사용자 스스로 조심하는 수밖에 없지만, 2번과 3번은 우리가 서비스를 구축하면서 제공하는 보안 요구사항이 될 수 있죠. 이번 글은 3번 항목에 대해서 다룹니다.
비밀번호 히스토리 관리의 요구사항
요구사항 검토하기
보안과 사용자 경험을 적절히 만족시키는 것이 좋습니다. 우선순위는 보안이어야 합니다. 사용자 경험은 대체로 사용감과 편의성을 위한 것으로 이해할 수 있고, 매출과 경쟁력을 높이는 요소입니다.
✅ 과거에 사용한 것과 동일한 비밀번호 사용을 N 개까지 막기
특히 비밀번호가 유출을 의심할 만할 때 변경되는 것을 대전제로 하면, 비밀번호를 다시 사용하는 것은 '유출되었을 가능성이 다른 것에 비해 높은' 비밀번호를 사용하는 것입니다.
예를 들어 구글은 사용자가 최근에 사용한 적이 있는 비밀번호 100가지를 다시 사용할 수 없도록 합니다.
❌ 과거에 사용한 것과 동일한 비밀번호 사용을 N 년 동안 막기
동일한 비밀번호의 재사용을 개수로 제한하는 의견처럼, 기간으로도 제한할지 검토할 수 있습니다. 예를 들어 10년 이내에 사용한 비밀번호만 제한하고, 그보다 오래된 비밀번호는 '충분히 오래 전에 사용했기 때문에' 보존할 필요가 없다고 생각하여 삭제하거나, 기존 히스토리가 새로운 히스토리 정책 운영 방식에 너무 맞지 않아 어차피 배제해야 하는 상황이 있을 수 있습니다.
이처럼 각자의 이유로, 오래된 비밀번호 이력을 삭제하거나 운영 방식에서 제외할 수 있습니다. 대부분은 운영상의 이유일 것 같습니다. 또는 개수 제한을 없애고 기간에 따라서만 제한할 수도 있지만, 과도하게 사용자 경험을 해치며, 사용자가 원하는 비밀번호를 택할 수 없게 만들 수도 있고, 비밀번호 변경 이력을 너무 자주 남겨 불필요한 데이터량을 오랫동안 유지해야 할 수도 있습니다.
우리는 이것이 보안 강화를 위한 선택은 아니라는 것을 알아야 합니다.
❌ 과거에 사용한 것과 '비슷한' 비밀번호 사용을 막기
동일한 것이 아니라 '유사한' 비밀번호의 재사용을 제한하는 요구사항은 개발자가 거부해야 합니다.
개발자가 이러한 요구사항을 거부해야 하는 이유는 다음과 같습니다.
비슷한 패턴을 찾기 위해 원문을 비교할 수 있으면 안 됩니다. 비밀번호는 반드시 단방향 암호화를 해서 저장하도록 되어 있습니다.
암호학적으로 저항성이 없는 해시를 사용하는 것도 안 됩니다. 암호학적 해시 함수는 눈사태 효과에 따라 해시 값으로 입력값을 유추할 수 없으며(역상 저항성), 해시 값의 유사도로 입력값의 유사도를 알 수 없어야 합니다. 만약 암호학적이지 않은 해시를 사용하면 유출 사고의 피해가 크게 증폭되어 서비스 보안에 치명적입니다. 정보 보호에 최선을 다하지 않은 회사에 큰 책임이 생깁니다.
따라서 기술적으로 '과거에 사용한 비밀번호와 비슷한 비밀번호의 재사용 막기'는 주요 보안 요구사항을 우회하는 형태가 되기 때문에, 그 구현 가능성과 보안성 등에 꼼꼼한 검토가 필요하죠.
🔍 처음 사용된 것이 최근 24시간 이내인 비밀번호는 허용하기 (논의)
처음 사용된 것이 최근 24시간 이내인 비밀번호를 다시 선택할 수 있도록 허용하는 것은 사용자 경험 및 보안에서 모두 긍정적인 도움이 될 수 있습니다.
이 내용은 저의 개인적인 견해이며, 전문가들이나 전문 기관에 의해 충분히 논의되지 않았습니다. 따라서 참고 정도로 봐 주시고, 필요에 따라 신뢰할 수 있는 자료로 삼아 주시기 바랍니다.
기억하기 쉬우면서도 안전한 비밀번호를 되찾기
'사용자에게 기억하기 어려운 새로운 비밀번호를 자주 요구하는 것'이 보안에 크게 도움이 되지 않는다는 것을 이야기했습니다. 우리는 사용자가 오늘 사용을 시도한 비밀번호 중 충분히 길고 안전하여 유추하기 어려우면서도 기억은 잘할 수 있는 비밀번호를 되찾게 함으로써, 사용성과 안전을 모두 갖춘 비밀번호를 선택하도록 할 수 있습니다.
오늘 시도한 비밀번호 중 하나가 충분히 안전하면서도 기억에 잘 남는다면, 사용자는 그 비밀번호를 다시 사용하고 싶을 수 있습니다.
특히 사람의 기억 방식 중에는 '초두효과(primacy effect)'라는 개념이 널리 사용되고 있으며, 먼저 선택한 비밀번호가 기억에 오래 남을 수 있습니다. 사용자는 잘 기억할 수 있는 비밀번호 중 가장 안전하다고 생각하는 암호를 신뢰하겠죠. 이 비밀번호를 허용함으로써 우리는 사용자가 더 안전한 비밀번호를 유지하도록 도울 수 있습니다.
사용자는 기억하기 어려운 마지막 비밀번호를 강요받을 때, 더 짧고 예측 가능한 비밀번호로 전환할 가능성이 있습니다.
따라서 사용자가 기억하기 어려운 최근 비밀번호를 대체하기 위한 선택지로, 짧고 유추하기 쉬운 비밀번호를 선택하게 하는 것보다 '안전하고 기억할 수 있다고 생각한 비밀번호'를 다시 선택할 수 있도록 하는 것이 더 안전하고 합리적인 정책으로 보이는 것입니다.
처음으로 사용하여 유출되지 않은 비밀번호를 다시 선택하는 것은 보안 리스크를 증가시키지 않습니다. 이때 안전한 비밀번호를 되찾을 권리를 사용자에게 주는 것은 합리적이며, 그런 선택의 실행을 지나치게 어렵게 만들 필요가 없습니다.
사용자 본인에 의한 비밀번호 히스토리 무력화 줄이기
사용자가 최근 비밀번호를 사용하기 위해, 비밀번호를 N+1 번 변경하도록 유도하지 않습니다. 즉, 비밀번호 히스토리를 사용자가 자발적으로 무력화시키는 상황을 줄일 수 있습니다.
예를 들어 일부 구글 사용자는 방금 처음으로 사용한 비밀번호를 되찾기 위해 비밀번호를 추가로 100번 더 바꾸곤 합니다. 이는 비밀번호 히스토리를 완전히 초기화하는 것과 거의 같으며, 이후 사용자의 동일 비밀번호 재사용을 제한하는 효과를 한 차례 거의 잃습니다.
사용자에 의한 비밀번호 히스토리 무력화는 비밀번호 히스토리 정책의 취지를 완전히 빗겨 가는 것이며, 이 정책으로 보안을 강화하기 위해서는 사용자의 무력화 시도를 유도하지 않도록 잘 검토해야 합니다.
✅ 사용자에게 피드백 제공
사용자에게 이 비밀번호를 사용할 수 없는 이유가 무엇인지, 또는 이 비밀번호가 위험한 이유가 무엇인지 안내하는 것이 좋습니다.
이 피드백에서 과거와 동일한 비밀번호를 다시 사용하면 안 된다는 것을 알려 줄 수 있고, 어떠한 대안을 사용하는 것이 좋은지 등 사용자의 가이드가 되어 줄 수도 있습니다.
✅ 비밀번호 이력을 비교하는 데에 필요한 시간을 N초 이하로 줄이기
Performance ⚡ vs Security 🔒
UX(사용자 경험)보다 먼, 보안보다는 가까운.
보안을 해치지 않는 한 사용자 경험을 보장할 수 있습니다.
보안이 중요한 작업은 사용자들이 어느 정도 지연을 납득할 수 있습니다. 특히 요청을 안전하게 처리하고 있다고 피드백을 제공하여 신뢰할 수 있는 보안이 적용되고 있음을 알림으로써 사용자 경험을 개선할 수 있습니다.
사용자 경험과 지연 시간에 뚜렷한 기준이 통일되어 있지는 않지만 대략적인 안내는 다음과 같습니다.
(페이지 로딩은 1~3초 이내. 3초를 넘으면 사용자가 사이트를 이탈하는 비율 급증.)
60~100 밀리초 이내: 채팅이나 게임 등 실시간성이 필요한 기능은 이런 지연시간이나 그 이하를 목표로 합니다.
100~500 밀리초 이내: 일반적인 기능들은 작업량에 따라 이런 목표 시간을 설정합니다. 처리량이 많은 작업은 초 단위 목표 시간이 되기도 하며, 다른 방식으로 사용자 경험을 개선할 수 있습니다.
1~5초 이내: 보안 때문에 성능을 양보해야 한다면, 보통 3초나 5초까지 기다리게 할 수 있습니다.
(대형 서비스는 5초까지 소요해도 신뢰를 유지하지만, 오히려 작은 서비스는 조금 더 신속한 것을 목표로 할 수 있습니다. 이는 기능에 따라서도 차이가 생깁니다.)5초 초과: 서비스 품질에 대한 의심이 보안에 대한 인식으로 연결되지 않도록 하는 것이 중요합니다.
따라서 비밀번호 이력을 비교하고 비밀번호 암호화와 등록까지 모두 완료하는 과정이 3초나 5초 이내가 되도록 합니다.
✅ 비밀번호 이력을 비교하는 동안 발생하는 서버의 부하를 줄이기
특히 하드웨어 저항성이 있는 단방향 암호화 함수를 사용하고 있다면, 비밀번호 이력을 하나씩 비교하는 것은 서버의 부하를 늘립니다. 만약 빠른 응답을 위해 많은 솔트로 동시에 여러 암호화를 한다면, 스스로 DoS 공격을 받는 서비스를 만드는 것일 수 있고, 정상적인 사용자 요청도 부하가 될 것입니다.
비밀번호 히스토리 설계하기
현대적인 비밀번호 암호화 함수의 성능 이슈 이해하기
요약
반복 해싱을 적용하며, 이 과정에서 일부러 해시 함수가 느리게 동작하도록 유도합니다.
가변 솔트를 적용하며, 모든 비밀번호의 솔트가 다르기 때문에 암호화를 각각 실행해야 합니다.
자세히 보기: 현대적인 비밀번호 암호화와 가변솔트, 반복 해싱
https://blog.letsdev.me/password-encryption-concept-kor
키 스트레칭과 반복 해싱
최근 비밀번호의 암호화에 많이 사용하는 것은 BCrypt, PBKDF2, SCrypt, Argon2 등입니다. 이중 PBKDF2를 제외한 BCrypt, SCrypt, Argon2 등은 모두 공격자가 동시에 여러 비밀번호를 시도할 때 성능에 부하가 많이 생기도록 설계되어 매우 안전하다고 평가하죠.
오프라인 어택, 브루트포스 어택, 사전 공격(딕셔너리 어택)
비밀번호 해시 값이 유출된 사례는 생각보다 적지 않습니다. 레인보우 테이블이든, 브루트포스 공격이든, 브루트포스에서 '비밀번호일 가능성이 높은 문자 조합'부터 대입해 보는 딕셔너리 공격이든, 운영 서버에 바로 시도하는 것은 효과가 적거나 실행할 수 없기 때문에 해시 탈취 후 오프라인에서 시도합니다.
오프라인 어택은 해시를 탈취한 다음 본인의 환경에 암호화 프로그램을 만들어서, 동일한 해시가 있는지 비교하며 딕셔너리 어택을 하는 식으로 시도될 수 있습니다. 이렇게 하면 로그인 시도 횟수에 제한 없이 빠르게 많은 대입을 시도할 수 있기 때문입니다.
하드웨어 저항성
우리는 해시 값이 유출되더라도 비밀번호 노출의 리스크를 거의 없애기 위하여 하드웨어 저항성이 높은 함수를 선호합니다. 여기서 말하는 하드웨어 저항성은 고성능 장치(CPU, GPU, ASIC 등)를 사용하여 비밀번호를 빠르게 알아내려는 시도에 견디는 능력을 말합니다. 하드웨어 저항성이 높다면 동시에 여러 비밀번호를 암호화해 시도하는 것이 많은 리소스를 소모하기 때문에, 동시에 여러 비밀번호 대입 시도를 확연하게 낮출 수 있습니다.
가변 솔트(Dynamic Salting)
해시 함수는 같은 입력을 넣으면 같은 결과를 생성합니다. 이 특징은 정답표(레인보우테이블)를 만들 수 있게 하기 때문에, 보통은 솔트라고 하는 것을 입력 값에 첨가하여 '같은 입력'이 아니게 만들죠.
솔트를 서비스 전체에서 동일하게 사용하는 것은 이제 솔트라고 부르지 않고, '페퍼'라고 부릅니다. 요즘 솔트는 암호화할 때마다 다른 값을 사용할 수 있고, 이것을 영어로는 동적인 솔팅(Dynamic salting), 한국어로는 '가변 솔트'라고 번역합니다.
비밀번호 비교는 양쪽 값이 모두 단방향 암호화된 상태에서 하는데, 만약 비밀번호 100개에 가변 솔트를 적용하면, 그 비밀번호 100개와 비교하기 위해서는 암호화를 100번 수행해야 합니다.
암호화 한 번에 1초 가량을 소요하도록 목표를 정했다면 총 100초 정도가 필요하겠죠.
보안 요구사항: 필요한 보안 강도 파악하기
인증용 비밀번호와 히스토리용 해시 값이 동일해야 할 필요는 없음
저는 예전에 이 솔루션을 생각해 냈을 때, 이것이 현대에 매우 일반적이거나, 또는 이것보다 효율적이고 유명한 방식이 보편화되어 있을 것이라고 생각했습니다. 그래서 제가 생각한 것이 얼마나 맞을지 곳곳에 물어보고 다니기 바빴죠.
그렇게 최근에도 위 딜레마의 솔루션을 떠올리는 분들이 계신지 종종 찾아 보았습니다. 사람들은 대부분 저 문제를 한 번에 해결할 방법을 찾지 못했습니다만, 몇 번의 오답 피드백 이후 결국 답에 근접하는 분도 있었습니다. 사실 편견 하나만 깨면 생각보다 간단하게 해결할 수 있는 문제였죠.
그리고 동일한 비밀번호의 재사용을 최근 100개까지 방지하는 기업이면, 이미 좋은 솔루션을 적용 중일 것 같습니다. 어쩌면 그곳은 이미, 제가 떠올린 방식의 실사 환경일지도 모르죠.
솔루션 찾기
솔루션의 키는 아주 작은 실마리에서 시작합니다. "가변 솔트를 적용하면 하나하나 새롭게 암호화한 후 비교하는 수밖에 없다."라는 전제죠. 그러면 논리적으로 어떻게 살펴 봐도 가변 솔트 환경에서는 절대로 비밀번호 히스토리를 한 번에 비교하는 것은 큰 욕심일 수 있었습니다. 사용자마다 고정 솔트를 사용하는 환경에서는 아주 쉬운 문제였을 텐데 말이죠. 그리고 떠올랐죠.
'잘 생각해 보면 비밀번호 히스토리는 로그인에 사용하는 게 아니잖아? 히스토리지.'
생각이 거기에 미치자 이후 솔루션 개척은 척척 이루어졌습니다. 인증용 암호는 가변 솔트를 적용하지만, 히스토리용 해시 값은 고정 솔트를 사용하는 대신 인증에 사용하지 않는 것으로 엄격하게 정하는 것이죠.
비밀번호 히스토리라고 해서 반드시 인증에 사용했던 해시 값을 그대로 기록해야 할 이유가 없었거든요.
암호화 방식을 구분하기
그래서 히스토리용 해싱을 할 때는 비밀번호 100개와 비교할 때 이슈가 되는 가변 솔트를 제거했습니다.
인증용 비밀번호 해시 값: BCrypt, SCrypt, Argon2 등과 가변 솔트를 적용하여 암호화합니다.
히스토리용 해시 값: 고정 솔트를 사용합니다. 히스토리에만 사용하고, 로그인에 사용하지 않습니다.
아래 자료처럼 가변 솔트는 인증용 암호에, 고정 솔트는 히스토리용에 사용하는 것이 첫 번째 키였죠.
사용자별 고정 솔트 운영
고정된 솔트라고 해서 서비스 전체에 같은 값을 사용할 필요는 없습니다. 어차피 사용자 단위로 비밀번호 히스토리가 구분되어 취급되기 때문에, 사용자 단위로 고정된 솔트를 운영하는 것이 안전하죠.
사용자의 고정 솔트를 필요할 때 유연하게 업데이트할 수 있도록 하면, 향후 추가적인 보안 조치를 열어 둘 수 있습니다.
사용자의 고정 솔트에 대한 추가적인 요구사항은 '해시 값 유출에 대비하기 1: 고정 솔트를 인증 DB에서 격리'에서 후술하겠습니다.
페퍼링
공격 준비 시간을 부여하는 게 되는 고정 솔트
고정 솔트의 위험성은 해시 값 유출을 늦게 알아차렸을 때 나타납니다. 만약 고정 솔트가 함께 유출되어 있다면, 공격자는 이 사용자의 비밀번호 히스토리에 대한 레인보우 테이블을 준비할 수 있습니다. 완전히 채운 레인보우 테이블은 아니더라도, 자주 사용할 만한 비밀번호 사전에 대해서는 준비할 수 있겠죠.
그렇게 주요 사용자 계정에 대해 공격을 준비할 시간이 생깁니다. 비밀번호 히스토리의 최신 값이 인증용 비밀번호와 같은 원문에서 암호화됐다면, 가변 솔트를 사용한 인증용 테이블의 비밀번호는 해시 값 유출 이후 공격을 시작하여야 하는 데에 비해, 고정 솔트를 사용한 히스토리 테이블은 미리 만들어 둔 값들과 비교해 빠르게 공략할 수 있게 됩니다.
데이터베이스 유출로는 노출되지 않는 페퍼
우리는 사용자의 솔트와 해시 값이 유출되어도 함께 유출되지 않는 '페퍼'를 추가할 수 있습니다. 페퍼는 솔트와 거의 비슷하지만, 서비스 내에서 유일한 값으로 볼 수 있습니다. 그리고 데이터베이스에 저장하지 않죠. 데이터베이스에서 인증용 비밀번호의 해시 값과 가변 솔트 등이 함께 유출되어도, 추가된 페퍼를 알 수 없다면 솔트로 비밀번호를 알아맞혀도 페퍼 때문에 전혀 다른 결과가 되죠.
페퍼는 공격자가 알 수 없는 값이라는 것이 핵심이며, 시크릿키 등으로 대체될 수 있습니다.
이하 작성하고 있습니다.
반드시 시기를 기록
다른 이유가 없더라도 히스토리니까 시각을 기록하는 것은 당연한 이야기입니다. 이 히스토리를 나중에 어떻게 참고해서 사용할지 모르니까요.
여기서는 보안상으로도 시기가 기록되어야 하는 이유가 있는데, 데이터베이스와 격리된 정보와 상호작용 때문입니다. 히스토리 테이블 관리에 참여하는 페퍼처럼 일부 정보는 데이터베이스에 종속되는 데이터가 아니죠. 오히려 데이터베이스와 격리되어 보안을 강화합니다.
이처럼 데이터베이스에 종속되지 않은 데이터는 적용 시기를 알 수 있어야 함.
암호화 방식이나 솔트를 히스토리 테이블 등 인증 DB에 저장하지 않고, 페퍼도 업데이트 하기 편하도록.
해시 값 유출에 대비하기 1: 고정 솔트를 인증 DB에서 격리
해시 값 유출 시 비밀번호 히스토리 테이블은 인증용 테이블에 비해서 공략이 쉬울 수 있음. 특히 인증용 해시 값이 아니기 때문에 보안 수준을 낮추고 서버의 성능을 보장하려는 곳도 있을 수 있음. (이에 대비해 충분한 암호화를 적용하고 있지 않을 수 있음.)
이때는 적어도 솔트라도 알 수 없게 하면 좋음.
별도 저장 공간
인증 DB의 유출로는 사용자 고정 솔트가 유출되지 않음. 유연하게 업데이트 가능.
비밀번호 변경은 자주 사용하는 기능이 아니기 때문에 꼭 비용이 비싼 저장소를 쓸 필요도 없음.
복잡한 연산으로 고정 솔트 생성
서버만 아는 값을 시드로 사용. 이 값을 사용자의 식별 가능한 정보와 결합해서 복잡한 연산을 통해 솔트 생성.
여전히 업데이트 가능한 솔트
보안 이슈가 있을 때나 주기적인 갱신이 필요할 때 사용자의 고정 솔트를 하나씩 업데이트할 필요 없이, 이 '서버만 아는 값'을 업데이트할 수 있음. 별도 저장 공간을 사용할 때에 비해서 개별 업데이트는 X
데이터베이스에 보존되지 않아서 보통 환경변수 등으로 쓰니까. 솔트만 보면 스테이트리스한 관리.
해시 값 유출에 대비하기 2: 히스토리에 인증용 암호는 없게
히스토리 테이블에 '현재 암호'는 보존하지 않는 전략
새 비밀번호로 변경할 때, 기존 비밀번호를 히스토리에 넣고, 새 비밀번호는 히스토리에 넣지 않음.
히스토리 테이블 해시 값 털어도 어차피 로그인에 쓰이는 비밀번호를 알 수 있는 해시 값은 전혀 없음.
비밀번호 변경 요청에는 '기존 비밀번호'를 받아서 인증하기
보안상으로도 이게 좋음. 로그인이 되어 있다고 이런 민감한 기능도 바로 수행하기보다, 비밀번호 인증과 함께 수행하거나, 비밀번호 인증 후 임시 토큰을 제공.
임시 토큰을 제공하는 경우 아래 방식 중 '기존 비밀번호를 히스토리에 넣기'를 위해 별도로 임시 저장이 되어야 함. (ex: 사용자에게 임시 토큰을 주고, 기존 비밀번호는 단방향 암호화하여 redis 등에 저장)
이 토큰은 JWT가 아니며(원문과 함께 전달하지 않음), 만료 시간이 짧아야 함. 당연히 리프레시 없음.
인증 완료된 암호를 그때 히스토리에 해싱해서 넣기
비밀번호를 함께 받아서 처리할 때
기존 암호로 인증도 됐고 새 암호가 중복도 없으면, 기존 암호는 해싱해서 히스토리 테이블에 넣기.
인증을 완료한 후 임시 토큰으로 인가하여 비밀번호를 변경할 때
인가됐으니 새 비밀번호가 중복이 있는지 보고, 중복이 없으면 임시로 저장된 기존 비밀번호의 해시 값을 히스토리 테이블에 저장.
해시 값 유출에 대비하기 3: 하드웨어 저항성에 특화
암호화 강도가 인증용에 비해 낮아도 되는 것은 당사 사이트 기준(그 사이트의 로그인에는 안 쓰니까.)
사용자의 비밀번호 히스토리는 다른 사이트에서 사용하는 비밀번호일 수도 있음.
따라서 과도하게 낮은 보안 강도는 안 됨.
Argon2의 세 가지 옵션의 방어 특화
Argon2d: GPU 공격에 특화
Argon2i: 사이드 채널 어택에 특화
Argon2id: GPU 공격과 사이드 채널 어택에 골고루 특화
비밀번호 히스토리에 셋 중 하나를 쓴다면
'비밀번호 바꾸기' 요청에서 일단 한 번은 암호화 하니까. 이때 연산 평균시간 등 사이드채널 공격이 생길 수도 있지 않을까 -> Argon2id가 나아 보일 수 있음. -> 근데 잘 생각해 보면 -> 어차피 인증용 암호 받기로 해서 -> 인증에 성공해야 비밀번호 히스토리랑 비교하는 거라서 -> 사이드채널 어택보다는 그냥 해시 값 유출에 대비 -> Argon2d 선택
Argon2d + 고정 솔트 (비표준)
완성된 라이브러리에서는 자동으로 솔트 생성하고 있을 수도 있음.
대략적인 권장의 예시 1: 인증과 함께 수행
요청 및 암호화
비밀번호 변경 요청에서 기존 비밀번호와 새 비밀번호를 모두 받음.
인증용 암호화에는 Argon2id 사용을 권장할 수 있음.
히스토리용 암호화는 Argon2d 사용을 권장할 수 있으며, 이는 인증에 사용되어선 안 됨.
Steps
1단계: 기존 비밀번호 인증(Argon2id)
기존 비밀번호와 새 비밀번호를 모두 받아 둠.
기존 비밀번호로 인증.
여기도 인증을 포함하기 때문에 인증 비밀번호 시도 루트가 됨. 따라서 시도 횟수 제한 필요.
2단계: 히스토리 검토(Argon2d 및 고정 솔트)
Argon2 함수는 가변솔트가 표준이므로, 비표준적인 사용이며 일부분 직접 구현해야 할 수 있음.
인증에 성공해야 이 단계를 수행하므로 사이드 채널 어택 가능성이 거의 없음.
(반드시 Argond2i나 Argon2id를 택하지 않아도 됨.)해시 값 유출에 대한 대비를 더 강화하는 것이 좋음.
(해시 값 유출에 한하여 Argon2d가 Argon2id보다 저항성이 있음.)
3단계: 암호화한 해시 저장
새 인증용 비밀번호는 암호화하여 인증 테이블에 저장(Argon2id)
기존 비밀번호는 비밀번호 히스토리에 저장(Argon2d 및 사용자 고정 솔트)
응답까지 최소 세 번의 암호화를 포함하기 때문에, 이를 고려한 성능 요구사항 체크 필요함.
암호화 1: 기존 비밀번호 인증(Argon2id)
암호화 2: 새 비밀번호 히스토리 비교(Argon2d)
암호화 3: 새 비밀번호 인증용 암호화(Argon2id) - 완료 후 사용자에게 응답 가능
암호화 4: 기존 비밀번호 히스토리용 암호화(Argon2d)
대략적인 권장의 예시 2: 인증 후 토큰으로 인가 수행
이 토큰은 JWT가 아님.
원문과 함께 전달하면 안 되기 때문(이거 JWT로 하라고 하면 분명 누군가는 비밀번호 담으려고 함).
stateless일 필요 없으니까.
각 암호화 예시
인증용 비밀번호: Argon2id
히스토리용 해시: Argon2d
토큰: Secure Random (암호학적으로 안전한 랜덤 함수)
1단계: 재인증 및 임시 토큰 발행
1-1: 재인증 요청 처리
1-2: 토큰 발행 (응답)
1-3: 기존 비밀번호의 히스토리용 해시 임시 저장
2단계: 토큰 인가 및 비밀번호 변경
2-1: 토큰과 새 비밀번호를 담아서 비밀번호 변경 요청
2-2: 토큰 인가
기존 비밀번호를 암호화해서 인증하는 것보다 빠름.
2-3: 새 비밀번호가 히스토리와 겹치는지 비교
2-4: 새 비밀번호를 인증 테이블에 저장
이때 응답.