패스키는 비밀번호를 보내지 않는다: 서명으로 로그인하기
요즘은 비밀번호 대신 패스키로 로그인하라는 안내를 자주 봅니다. 지문이나 휴대전화 화면 잠금을 풀면 로그인이 끝나고, 비밀번호는 어디에도 입력하지 않습니다 [S1]. 서버는 무엇을 받고 «나» 라고 믿을까요?
비밀번호는 비밀을 보낸다
비밀번호 로그인에서는 내가 아는 비밀을 서버로 보냅니다. 표준은 서버가 비밀번호를 비밀번호 해싱으로 바꿔 저장하도록 정합니다 [S2]. 그러면 서버는 받은 비밀번호로 다시 계산해 견줍니다.
그래서 비밀이 새는 길이 있습니다. 서버가 털리면 공격자는 저장된 값을 놓고 비밀번호를 하나씩 맞혀 볼 수 있습니다 [S2]. 진짜처럼 꾸민 가짜 사이트에 입력하면 비밀번호가 그대로 넘어갑니다 [S3]. 비밀번호는 로그인할 때마다 같은 값을 내밀기 때문에, 한 번 손에 넣은 값으로 다시 들어갈 수 있습니다 [S2].
서명으로 로그인
패스키는 디지털 서명에 쓰는 키 쌍입니다 [S3]. 가입할 때 기기가 개인 키와 공개 키를 만들고, 서버는 개인 키 대신 공개 키를 저장합니다. 개인 키는 기기가 지키고 다른 누구에게도 보이지 않습니다 [S4].
로그인할 때는 서버가 무작위 값 하나를 냅니다. 이 값을 챌린지라고 합니다. 기기는 지문이나 화면 잠금으로 나를 확인한 뒤, 개인 키로 챌린지에 서명합니다. 서버는 저장해 둔 공개 키로 그 서명을 검증합니다 [S4] [S3].
서명은 개인 키를 가진 쪽만 만들 수 있고, 공개 키만 있으면 누구나 검증할 수 있습니다 [S5]. 따라서 서버와 기기가 함께 가진 비밀은 없습니다 [S3].
| 비밀번호 | 패스키 | |
|---|---|---|
| 보내는 것 | 비밀번호 그대로 | 챌린지에 한 서명 |
| 서버에 남는 것 | 비밀번호의 해시 값 | 공개 키 |
| 서버가 털리면 | 해시 값으로 비밀번호를 맞혀 볼 수 있음 | 공개 키로는 서명을 못 만듦 |
| 가짜 사이트에서 | 비밀번호가 넘어감 | 실제로는 가짜 사이트에 패스키가 아예 나오지 않음. 시뮬레이터처럼 서명을 얻어 와도 진짜 사이트가 거절 |
아래 시뮬레이터는 브라우저의 서명 기능(Web Crypto)으로 앞의 로그인 차례를 흉내 냅니다. 실제 패스키 기능(WebAuthn)을 부르지 않고, 서버가 하는 검증 가운데 세 가지만 옮겼습니다 (교육용).
…
다른 사람이 해 보면
서명한 내용의 챌린지
서명한 내용의 사이트 주소
서명
왜 매번 다른 값일까
누가 서명을 엿봐 두었다가 다음에 그대로 내밀면 어떨까요? «지난 서명 다시 보내기» 를 눌러 보세요. 서명 자체는 진짜라 공개 키로 검증됩니다. 하지만 그 서명이 덮은 챌린지는 서버가 이번에 낸 값이 아닙니다. 챌린지를 바꿔 넣으면 서명이 맞지 않게 됩니다. 서버는 챌린지를 매번 무작위로 새로 내고, 서명한 내용의 챌린지와 맞춰 봅니다. 이렇게 지난 응답을 되풀이하는 재전송 공격을 막습니다 [S4] [S6].
서버가 털리면, 가짜 사이트라면
서버가 털려도 로그인 검증에 쓰는 값은 공개 키뿐입니다. 공개 키로는 검증만 할 수 있고, 서명은 개인 키를 가진 쪽만 만들 수 있습니다 [S5]. «공개 키만 가진 사람의 서명» 은 공격자가 자기 키로 서명해 보는 장면입니다. 이 서명은 서버에 저장된 공개 키로 검증되지 않습니다.
가짜 사이트는 어떨까요? 브라우저는 서명할 내용에 지금 열린 사이트 주소를 넣고, 서버는 그 주소가 자기 주소인지 확인합니다 [S4]. 가짜 사이트가 진짜 챌린지를 받아 내 서명을 얻어도, 주소가 달라 거절됩니다. 실제로는 패스키가 가입한 사이트에 묶여 있어서, 가짜 사이트에서는 서명부터 나오지 않습니다 [S4] [S2]. 위 시뮬레이터의 «가짜 사이트에서 얻은 서명» 은 이 첫 관문을 건너뛰었다고 치고, 둘째 관문(서버의 주소 확인)에서 걸리는 모습을 보여 줍니다.
서명이 말해 주지 않는 것
✓ 가 알려 주는 것은 «가입할 때 받은 그 공개 키의 짝으로 서명했다» 까지입니다. 그 공개 키가 정말 그 사람 것인지는 서명으로는 알 수 없고, 가입할 때 그 공개 키를 내 계정에 등록하는 절차가 맡습니다 [S5]. 또 기기를 모두 잃으면 서명할 수 없습니다. 그래서 패스키를 여러 기기에 함께 저장해 두거나, 따로 보안 키를 마련해 둡니다 [S3].
이 글의 계산과 확인
시뮬레이터는 브라우저의 Web Crypto 로 ECDSA P-256(SHA-256) 키 쌍을 만들고 서명합니다. 표준이 따로 정하지 않았을 때 먼저 쓰는 방식(ES256)과 같습니다 [S4]. 서명하는 바이트도 표준대로 authenticator data 뒤에 client data 의 SHA-256 해시 값을 붙여 만들고, client data 에 챌린지와 사이트 주소를 넣습니다 [S4]. 서버의 검증은 표준이 정한 여러 단계 가운데 서명·챌린지·주소 셋만 옮겼습니다. 키는 페이지를 열 때마다 새로 만들고 어디에도 보내지 않습니다. 교육용 — 실제로 쓰지 말 것.
검증 함수는 표준의 시험 벡터(WebAuthn Level 3 §16.2)의 실제 서명을 통과시키고, 그 챌린지·서명·주소를 하나씩 고치면 거절합니다. Node 의 crypto(OpenSSL)와 서로 서명하고 검증해 맞춰 봤습니다 (verify.mjs 19개 통과).
출처
- [S1] Google 계정 고객센터 — 비밀번호 대신 패스키로 로그인하기
- [S2] NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management (2025) — §3.1.1.2, §3.2.5, §3.2.7
- [S3] FIDO Alliance — Passkeys (FAQ)
- [S4] W3C, Web Authentication Level 3 (2026) — §1, §4, §5.1.3, §5.8.1, §7.2, §13.4.3, §13.4.9, §16.2
- [S5] NIST FIPS 186-5, Digital Signature Standard (2023) — 머리말 3, §3
- [S6] Google for Developers — 서버 측 패스키 인증