패스키는 비밀번호를 보내지 않는다: 서명으로 로그인하기

요즘은 비밀번호 대신 패스키로 로그인하라는 안내를 자주 봅니다. 지문이나 휴대전화 화면 잠금을 풀면 로그인이 끝나고, 비밀번호는 어디에도 입력하지 않습니다 [S1]. 서버는 무엇을 받고 «나» 라고 믿을까요?

비밀번호는 비밀을 보낸다

비밀번호 로그인에서는 내가 아는 비밀을 서버로 보냅니다. 표준은 서버가 비밀번호를 비밀번호 해싱으로 바꿔 저장하도록 정합니다 [S2]. 그러면 서버는 받은 비밀번호로 다시 계산해 견줍니다.

비밀번호 로그인 나는 비밀번호를 그대로 서버로 보낸다. 서버는 받은 비밀번호로 해시 값을 다시 계산해 저장된 값과 견준다. 같은 비밀번호를 진짜처럼 꾸민 가짜 사이트에 입력하면 비밀번호가 그대로 넘어간다. 나 진짜 서버저장: 비밀번호의해시 값 비밀번호 그대로 다시 계산해 견줌 → ✓ 비밀번호 그대로 가짜 사이트비밀번호를 손에 넣음
그림 1. 비밀번호 로그인. 진짜 서버에 보내든 가짜 사이트에 보내든, 비밀번호 자체가 넘어갑니다.

그래서 비밀이 새는 길이 있습니다. 서버가 털리면 공격자는 저장된 값을 놓고 비밀번호를 하나씩 맞혀 볼 수 있습니다 [S2]. 진짜처럼 꾸민 가짜 사이트에 입력하면 비밀번호가 그대로 넘어갑니다 [S3]. 비밀번호는 로그인할 때마다 같은 값을 내밀기 때문에, 한 번 손에 넣은 값으로 다시 들어갈 수 있습니다 [S2].

서명으로 로그인

패스키는 디지털 서명에 쓰는 키 쌍입니다 [S3]. 가입할 때 기기가 개인 키와 공개 키를 만들고, 서버는 개인 키 대신 공개 키를 저장합니다. 개인 키는 기기가 지키고 다른 누구에게도 보이지 않습니다 [S4].

로그인할 때는 서버가 무작위 값 하나를 냅니다. 이 값을 챌린지라고 합니다. 기기는 지문이나 화면 잠금으로 나를 확인한 뒤, 개인 키로 챌린지에 서명합니다. 서버는 저장해 둔 공개 키로 그 서명을 검증합니다 [S4] [S3].

패스키 로그인 서버는 가입 때 받은 공개 키만 가지고 있다. ① 서버가 무작위 값인 챌린지를 기기로 보낸다. ② 기기는 챌린지와 지금 열린 사이트 주소를 묶어 개인 키로 서명한다. ③ 기기는 서명을 서버로 보낸다. ④ 서버는 공개 키로 서명을 검증한다. 내 기기개인 키 (서버로 안 보냄) 서버공개 키 (가입 때 받음) ① 챌린지 (무작위 값) ② 개인 키로 서명챌린지 + 지금 열린 사이트 주소 ③ 서명 ④ 공개 키로 검증 → ✓
그림 2. 패스키 로그인. 서버로 가는 것은 서명과, 챌린지·사이트 주소처럼 서명한 내용입니다. 개인 키는 서버로 보내지 않습니다 [S4].

서명은 개인 키를 가진 쪽만 만들 수 있고, 공개 키만 있으면 누구나 검증할 수 있습니다 [S5]. 따라서 서버와 기기가 함께 가진 비밀은 없습니다 [S3].

비밀번호패스키
보내는 것비밀번호 그대로챌린지에 한 서명
서버에 남는 것비밀번호의 해시 값공개 키
서버가 털리면해시 값으로 비밀번호를 맞혀 볼 수 있음공개 키로는 서명을 못 만듦
가짜 사이트에서비밀번호가 넘어감실제로는 가짜 사이트에 패스키가 아예 나오지 않음. 시뮬레이터처럼 서명을 얻어 와도 진짜 사이트가 거절
표 1. 두 로그인의 차이 [S2] [S3] [S4].

아래 시뮬레이터는 브라우저의 서명 기능(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개 통과).

    출처

    1. [S1] Google 계정 고객센터 — 비밀번호 대신 패스키로 로그인하기
    2. [S2] NIST SP 800-63B-4, Digital Identity Guidelines: Authentication and Authenticator Management (2025) — §3.1.1.2, §3.2.5, §3.2.7
    3. [S3] FIDO Alliance — Passkeys (FAQ)
    4. [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
    5. [S5] NIST FIPS 186-5, Digital Signature Standard (2023) — 머리말 3, §3
    6. [S6] Google for Developers — 서버 측 패스키 인증