Security · Authentication · Passkey

패스키 - 보낼 비밀이 없으면 훔칠 것도 없다

비밀번호가 새는 사고는 전부 '비밀을 보낸다'는 한 가지에서 나온다. 안 보내고 증명하는 방법과, 그 대신 무엇이 어려워지는지 본다.

·인증과 인가 5편
목차
  1. 우리 서버는 멀쩡한데 계정이 털린다
  2. 문제는 비밀을 보낸다는 것이다
  3. 도장은 건네지 않는다
  4. 등록: 무늬만 맡긴다
  5. 로그인: 서버가 준 종이에 찍는다
  6. 서버가 털려도 훔칠 게 없다
  7. 피싱이 원리로 막힌다
  8. 아이디도 안 치고 로그인된다
  9. 그래서 도장은 어디에 있나
  10. 대가: 도장을 잃어버리면
  11. 그래서 실무에선 어떻게 쓰이나
  12. 정리

지금까지는 로그인이 끝난 다음 이야기였다. 이번엔 그 로그인 순간으로 돌아간다.

우리 서버는 멀쩡한데 계정이 털린다

다른 회사에서 유출 사고가 났다는 뉴스가 나온다. 며칠 뒤 우리 서비스에서 로그인 실패가 폭증하고, 몇몇 계정은 실제로 뚫린다.

우리 DB는 안 털렸다. 그런데도 뚫린다. 사람들이 같은 비밀번호를 여기저기 쓰기 때문이다. 남의 집에서 새어 나온 목록을 우리 로그인 창에 그대로 넣어보는 것만으로 일부가 열린다.

두 번째 장면도 있다. 진짜와 똑같이 생긴 로그인 페이지가 있고, 사용자가 거기에 아이디와 비밀번호를 넣는다. 우리 서버는 아무 일도 안 했는데 비밀번호는 이미 남의 손에 있다.

문제는 비밀을 보낸다는 것이다

두 장면의 뿌리는 하나다. 비밀번호 방식은 비밀 그 자체를 서버로 보낸다.

그래서 세 가지가 따라온다.

어디서 새나왜 새나
서버검증하려면 뭔가를 저장해둬야 한다
사용자사람이 기억해야 하니 짧아지고 재사용된다
중간가짜 사이트에도 그대로 보낼 수 있다

비밀번호 저장 편이 첫 줄을 다뤘다. 해시하고, 솔트를 섞고, 일부러 느리게 만든다. 잘 하면 유출돼도 시간을 벌 수 있다. 하지만 그건 새어도 덜 아프게 하는 일이지 안 새게 하는 일이 아니다.

둘째 줄과 셋째 줄은 아예 손댈 수가 없다. 사용자가 어디에 무엇을 쓰는지 우리는 모르고, 사용자가 가짜 페이지에 비밀번호를 넣는 것도 우리 서버는 알 방법이 없다.

그래서 질문을 바꾼다. 비밀을 잘 지키는 대신, 아예 안 보내고 증명할 수는 없나?

도장은 건네지 않는다

인감도장을 떠올려 보자.

계약할 때 우리는 도장을 상대에게 주지 않는다. 상대가 내미는 서류에 찍어서, 찍힌 그 서류만 돌려준다. 도장은 계속 내 손에 있다. 상대가 나중에 털려서 서류가 통째로 새어 나가도, 그 서류로 새 도장을 깎을 수는 없다.

그러려면 미리 해둘 게 하나 있다. 인감을 등록해두는 것이다. 관공서에 도장의 무늬를 맡겨두면, 나중에 찍힌 서류가 진짜 내 도장인지 대조해볼 수 있다.

패스키가 정확히 이 구조다. 도장이 개인 키, 맡겨두는 무늬가 공개 키다. 공개 키 암호 편의 그 한 쌍이 여기서 쓰인다. 열쇠 두 개가 한 쌍이고, 하나로 잠근 걸 다른 하나로만 풀 수 있다는 그 성질이다.

비밀번호 - 비밀 그 자체를 보낸다
사용자 비밀번호
비밀번호
서버 검증용으로 보관
위험한 자리 셋 - 사용자 · 선 · 서버
패스키 - 도장은 건네지 않는다
사용자 기기 개인 키
찍은 서명
서버 공개 키만 보관
위험한 자리 하나 - 기기뿐

같은 그림에서 붉은 알약만 세면 된다. 위는 셋, 아래는 하나다.
아래 판의 선과 서버에 있는 것은 그걸로 되돌릴 수 없는 값이라 세지 않는다.

비밀번호는 비밀이 선을 건넌다. 패스키는 안 건넌다. 이 한 줄이 이 글의 전부이고, 나머지는 전부 여기서 따라 나온다.

등록: 무늬만 맡긴다

가입하거나 패스키를 추가할 때 일어나는 일이다.

사용자 기기가 그 사이트 전용으로 열쇠 한 쌍을 새로 만든다. 개인 키는 기기 안에 넣고 밖으로 안 꺼낸다. 서버에는 공개 키만 올라간다.

여기서 두 가지가 이미 해결된다.

서버가 저장하는 게 비밀이 아니다. 공개 키는 이름 그대로 공개돼도 되는 값이다. 검증만 되고 서명은 안 되기 때문이다. 그래서 이 DB가 통째로 유출돼도 그걸로 남의 계정에 로그인할 수 없다.

사이트마다 열쇠가 다르다. 사용자가 “같은 비밀번호를 여기저기 쓰는” 일 자체가 불가능하다. 기기가 사이트별로 따로 만들기 때문에, 재사용이 사용자의 습관 문제가 아니라 구조적으로 없는 일이 된다.

로그인: 서버가 준 종이에 찍는다

로그인은 왕복 한 번이다.

  1. 서버가 매번 다른 임의의 값(챌린지)을 만들어 보낸다. 상대가 내미는 빈 서류다.
  2. 기기가 그 값에 개인 키로 서명한다. 도장을 찍는다.
  3. 서버는 등록해둔 공개 키로 그 서명을 검증한다. 맞으면 그 사람이다.

서명은 개인 키로, 검증은 공개 키로 하는 그 구조 그대로다.

챌린지가 매번 다르다는 게 중요하다. 같은 값이면 한 번 오간 서명을 그대로 다시 보내는 것만으로 통과할 테니까. 매번 새 서류를 내밀기 때문에 지난번 도장이 찍힌 서류는 쓸모가 없다.

참고

여기서 오가는 건 서명뿐이고, 서명으로는 개인 키를 되찾을 수 없다. 그래서 통신을 통째로 엿들어도 얻을 게 없다. 비밀번호 방식이 HTTPS에 크게 기대는 것과 다른 지점이다. 다만 HTTPS를 빼도 된다는 뜻은 전혀 아니다. 애초에 브라우저가 HTTPS가 아닌 곳에서는 이 기능을 아예 안 내준다.

서버가 털려도 훔칠 게 없다

비밀번호 방식에서 우리가 하던 고민을 떠올려 보자. 어떤 해시를 쓸지, 솔트를 어떻게 넣을지, 몇 번 반복할지. 전부 **“털린 뒤에 얼마나 버티나”**를 계산하는 일이었다.

패스키에서는 그 계산 자체가 사라진다. 서버에 있는 건 공개 키라서, 털어가도 그걸로 할 수 있는 게 없다. 지켜야 할 비밀을 서버가 아예 안 갖고 있다.

이게 처음 두 줄을 한 번에 지운다. 서버에서 샐 비밀이 없고, 사이트마다 열쇠가 달라서 재사용도 없다.

피싱이 원리로 막힌다

남은 셋째 줄, 가짜 사이트가 진짜 재미있는 대목이다.

기기가 도장을 찍을 때, 서명하는 내용 안에 **“어느 사이트에 대고 찍는 것인지”**가 같이 들어간다. 그리고 이 값은 사람이 눈으로 읽고 판단하는 게 아니라 브라우저가 주소창에서 가져와 채운다.

같은 사용자 둘을 눈으로 구별하지 못한다
example.com
examp1e.com
기기의 열쇠고리 - 주소로 이름표가 붙어 있다 example.com 열쇠 examp1e.com 열쇠는 없다
서명이 도착한다 주소까지 함께 서명된다
아무것도 안 나간다 막은 게 아니라 찍을 열쇠가 없다

사용자는 두 주소를 구별하지 못하지만 기기는 이름표로 찾는다.
가짜 쪽은 경고가 뜨는 게 아니라 그냥 아무 일도 안 일어난다.

그래서 examp1e.com 같은 가짜 주소에서는 애초에 example.com용 열쇠가 선택되지 않는다. 사용자가 속아 넘어가도, 기기가 안 속는다.

HTTPS 편에서 자물쇠는 “그 주소로 제대로 연결됐다”까지만 말하고 그 주소가 가려던 곳인지는 말해주지 않는다고 했다. 패스키는 그 빈자리를 메운다. 주소가 다르면 열쇠가 안 나온다.

이게 다른 수단들과 결정적으로 갈리는 지점이다. 문자로 오는 인증번호나 앱에서 뜨는 여섯 자리 숫자는, 사용자가 가짜 페이지에 그대로 옮겨 적으면 그만이다. 사람의 주의력에 기대는 방어는 사람이 지치면 뚫린다. 패스키는 주의력을 안 쓴다.

아이디도 안 치고 로그인된다

사용자가 가장 먼저 체감하는 건 사실 보안이 아니라 이쪽이다.

비밀번호 방식의 로그인은 대개 세 동작이다. 아이디를 치고, 비밀번호를 치고, 2단계 인증을 켜뒀다면 문자로 온 숫자를 옮겨 적는다. 패스키는 이게 지문 한 번으로 끝난다.

아이디 칸이 사라지는 이유는 단순하다. 기기가 그 사이트용 열쇠를 이미 갖고 있고, 그 열쇠에 “이게 누구의 것인지”가 같이 붙어 있다. 그래서 서버가 “누구세요”를 물을 필요 없이, 기기가 열쇠를 내밀면서 함께 알려준다. 기기에 그 사이트 계정이 여럿 등록돼 있으면 그때만 고르는 화면이 뜬다.

2단계 인증이 사라지는 것도 공짜가 아니라 계산이 맞아서다. 인증은 흔히 아는 것(비밀번호), 가진 것(기기), 본인인 것(지문)의 세 갈래로 나누고, 둘 이상을 섞는 게 2단계 인증이었다. 패스키는 열쇠가 기기에 있으니 가진 것이고, 그걸 쓰려면 잠금을 풀어야 하니 본인인 것이다. 한 동작에 이미 두 갈래가 들어 있다.

참고

아이디를 안 치는 이 방식은 등록할 때 그렇게 만들어 두어야 동작한다. 옵션이 따로 있고, 안 켜고 만든 패스키는 사용자가 아이디를 먼저 알려줘야 기기가 열쇠를 찾는다. 기기 저장 공간을 조금 더 쓰는 대신 로그인 화면이 단순해지는 거래라, 처음 붙일 때 정하고 가는 게 낫다.

그래서 도장은 어디에 있나

여기서 비유가 한계에 닿는다. 인감도장은 사람이 손으로 찍지만, 패스키는 기기가 찍는다. 그러면 기기를 주운 사람도 찍을 수 있다는 뜻이 된다.

그래서 한 겹이 더 붙는다. 서명하기 직전에 기기가 지문이나 얼굴, 아니면 기기 잠금 PIN을 요구한다. “도장을 쥔 게 주인이 맞나”를 기기가 확인하는 것이다.

여기서 자주 생기는 오해를 하나 정리해둘 만하다. 지문은 서버로 가지 않는다. 지문은 기기 안에서 “이 도장을 써도 된다”는 잠금을 푸는 데만 쓰이고, 서버가 받는 건 여전히 서명뿐이다. 생체 정보를 서버가 보관하는 구조가 아니다.

도장이 실제로 사는 자리는 셋 중 하나다.

어디에성질
기기 보안 칩그 기기를 벗어나지 않는다. 가장 단단하지만 기기마다 따로 등록해야 한다
계정에 동기화OS나 브라우저 계정을 통해 내 기기들 사이에 복제된다. 편한 대신 그 계정이 새 울타리가 된다
보안 키(USB 등)물리적인 별도 장치. 가장 엄격한 곳에서 쓴다

가운데가 지금 가장 흔한 방식이고, “패스키”라는 이름이 대중화된 것도 이 동기화 덕이다. 다만 울타리가 우리 서비스 밖으로 옮겨간다는 건 알고 있어야 한다. 그 계정이 뚫리면 거기 담긴 패스키들이 같이 흔들린다.

대가: 도장을 잃어버리면

가장 아픈 자리는 여기다. 비밀번호는 잊어버려도 “재설정” 메일 한 통이면 되는데, 개인 키는 기억으로 복구할 수 있는 게 아니다.

기기를 잃어버렸는데 다른 데 등록해둔 게 없으면 들어갈 방법이 없다. 그래서 실무에서는 복구 경로를 반드시 같이 설계한다.

  • 패스키를 여러 개 등록시킨다. 폰과 노트북, 또는 보안 키를 하나 더.
  • 복구용 수단을 남겨둔다. 메일 인증이나 백업 코드.

그런데 여기에 함정이 있다. 복구 경로가 곧 가장 약한 고리가 된다. 패스키로 아무리 단단하게 잠가도, “메일로 재설정”이 열려 있으면 공격자는 그 메일을 노린다. 문을 하나 특별히 튼튼하게 만들었는데 옆에 쪽문이 그대로 있는 셈이다.

주의

그래서 패스키를 넣는 일은 로그인 화면만 바꾸는 작업이 아니다. 계정 복구 절차를 같이 올리지 않으면 전체 보안 수준은 그대로다. 실제로 공격은 늘 가장 싼 경로로 온다.

그래서 실무에선 어떻게 쓰이나

비밀번호와 함께 둔다. 한 번에 갈아치우는 경우는 드물다. 기존 로그인을 남겨두고 “이 기기에서 더 빠르게 로그인” 같은 형태로 패스키를 추가로 등록시킨 뒤, 쓰는 비율을 보면서 옮긴다.

로그인 다음은 하나도 안 바뀐다. 패스키는 “이 사람이 맞나”를 확인하는 순간까지만 담당한다. 확인이 끝난 뒤에 그 결과를 어떻게 이어갈지, 세션으로 갈지 토큰으로 갈지는 앞에서 본 그대로다. 인증 수단을 바꾼다고 세션 설계가 바뀌지는 않는다.

서버가 할 일은 생각보다 적다. 공개 키와 사용자를 짝지어 저장하고, 챌린지를 만들어 보내고, 돌아온 서명을 검증한다. 암호 연산을 직접 구현할 일은 없고 검증된 라이브러리를 쓴다. 대신 어느 사이트에 대고 찍은 서명인지 확인하는 부분은 절대 건너뛰면 안 된다. 그걸 안 보면 피싱이 막히는 이유가 통째로 사라진다.

기기 목록을 사용자에게 보여준다. 패스키는 기기마다 하나씩 생기므로 시간이 지나면 여러 개가 쌓인다. 사용자가 자기 목록을 보고 안 쓰는 걸 지울 수 있어야 한다. 폰을 바꿨는데 옛 폰의 패스키가 계속 살아 있으면 그것도 열린 문이다.

정리

무슨 문제를 푸나비밀을 보내기 때문에 생기는 세 가지: 서버 유출 · 재사용 · 피싱
어떻게 푸나개인 키는 기기에 두고, 서버가 준 챌린지에 서명해서 서명만 보낸다
서버가 저장하는 것공개 키. 털려도 그걸로 로그인할 수 없다
재사용사이트마다 열쇠가 따로 만들어져서 구조적으로 불가능하다
피싱서명에 사이트 주소가 묶인다. 브라우저가 채우므로 사람이 안 속아도 된다
지문·얼굴서버로 안 간다. 기기 안에서 개인 키 잠금을 푸는 용도다
2단계 인증따로 안 붙여도 된다. 가진 것(기기)과 본인인 것(잠금)이 한 동작에 들어 있다
가장 약한 곳계정 복구. 여기를 안 올리면 전체 수준이 안 오른다
로그인 이후안 바뀐다. 세션이냐 토큰이냐는 그대로 남는 질문이다

여기까지가 한 서비스가 자기 사용자를 인증하는 이야기였다. 그런데 “구글로 로그인”처럼 다른 서비스의 사용자를 데려오는 경우가 있다. 그때는 남의 집 비밀번호를 우리가 받아서는 안 된다.

다음 글에서 OAuth 2.0을 연다.