“비밀번호를 저장하지 않습니다”라고 웹페이지에 적는 데 드는 비용은, 다른 아무 문장이나 적는 비용과 정확히 같다. 0원이다. 그건 문장이지 보증이 아니다. 진실을 말하는 쪽도 그렇게 쓰고, 아닌 쪽도 똑같이 그렇게 쓴다. 화면 이쪽에서 보면 둘은 한 글자도 다르지 않다.
비밀번호를 “분석해 드립니다”라며 입력을 요구하는 모든 사이트의 근본 문제가 여기 있다. 설령 그 서비스가 흠잡을 데 없더라도 — 코드도 깔끔하고, 의도도 좋고, 사람들도 정직하더라도 — 당신에게는 그걸 알아낼 방법이 없다. 서버는 닫힌 상자다. 당신은 가진 것 중 가장 민감한 데이터를 그 안으로 던져 넣고, 반대편에서 약속한 대로 일이 벌어지기를 믿는 수밖에 없다.
그리고 흔히 놓치는 대목이 하나 있다. 저장하지 않겠다는 약속은, 약속한 사람 손에 달려 있지도 않은 일들로부터는 지켜 주지 못한다. 정직한 서버에도 아무도 들여다보지 않는 로그가 남고, 중간에 프록시가 끼고, 자동 백업이 돌고, 이번 주에 입사한 직원이 있고, 그냥 운 나쁜 날이 있다. 비밀번호는 이미 당신의 기기를 떠났다. 그 뒤에 벌어지는 일은 당신의 손 밖이고, 대개는 그들의 손 밖이기도 하다.
아무것도 믿을 필요가 없는 유일한 답
해법은 더 그럴듯하게 약속하는 게 아니다. 그 데이터를 필요로 하지 않는 것이다.
비밀번호가 브라우저 밖으로 나가지 않으면 “저 사람들 이걸로 뭘 하지?”라는 질문 자체가 성립하지 않는다. “저 사람들”이 없다. 받아 가는 서버도 없고, 그게 찍히는 로그도 없고, 그걸 담은 백업도 없고, 그걸 읽을 수 있는 직원도 없다. 신뢰를 더 잘 관리하는 게 아니라, 문제에서 아예 지워 버리는 것이다.
password.es가 하는 일이 그거다. 검사기는 당신이 입력한 것을 당신의 브라우저에서, 당신의 프로세서로 분석한다. 타이핑한 글자는 아무 데도 가지 않는다. 갈 데가 없기 때문이다.
무작위는 어디서 오는가
생성기도 발상이 똑같다. 무작위 비밀번호를 만들려면 난수가 있어야 하고, 난수를 구하는 방법은 둘뿐이다. 서버에 요청하거나, 브라우저에 요청하거나. 서버에 요청하는 건 앞뒤가 안 맞는다. 서버가 당신보다 먼저 당신의 비밀번호를 알게 되니까.
그래서 브라우저에 요청한다. crypto.getRandomValues()로. 암호학적 난수를 위해
웹 플랫폼이 표준으로 제공하는 API다. 바로 이런 용도로 존재하는 물건이다.
Math.random()은 게임에서 카드를 섞는 데는 쓸 수 있어도, 작정하고 덤비는
상대를 버텨야 하는 일에는 쓸 물건이 아니다. 난수를 만드는 주체는 당신의
기기에서 돌아가는 브라우저 자신이다. 우리는 관여하지 않는다. 결과는 당신
화면에 뜨고, 거기서 끝난다.
나를 믿지 않고 확인하는 방법
여기까지도 결국은 이 사이트를 만든 바로 그 사람들이 쓴 문단일 뿐이다. 즉, 방금 내가 의심하라고 했던 바로 그 물건이다. 그러니 믿지 마시라. 직접 보시라.
브라우저의 개발자 도구를 연다 — F12, 맥이라면 Cmd+Option+I. 네트워크
(Network) 탭으로 가서 열어 둔 채로 검사기에 들어간다. 페이지가 로드되는 게
보일 것이다. 이제 목록을 지우고, 비밀번호 입력칸을 클릭한 다음, 타이핑한다.
이제 보게 될 것은 다음과 같다. 여기서는 영업사원이 아니라 정확한 쪽을 택하는 게 맞다.
- 입력칸을 클릭하는 순간 요청이 하나 나간다. 당신의 비밀번호가 아니다 —
아직 아무것도 입력하지 않았으니까. 분석을 담당하는 라이브러리다.
zxcvbn-ko.min.js라는 파일이고, 그 안에 비교 대상이 되는 단어 목록, 이름, 키보드 패턴, 이미 알려진 비밀번호 목록이 들어 있다. 입력칸을 건드리는 즉시 password.es에서 내려받는다. 당신이 타이핑하는 동안 도착해 있도록 미리 가져오는 것이다. 같은 도메인에서 나가고, 정적 파일이고, 이 페이지를 여는 모든 사람에게 동일한 파일이다 — 사이트에는 언어별 버전이 여러 개 있고, 여기서 로드되는 건 한국어판이다. - 그 뒤로는 무엇을 입력하든 목록이 꿈쩍도 하지 않는다. 한 글자, 스무 글자, 지웠다 다시 쓰기, 긴 문장 붙여넣기. 요청은 0건. 카운터가 움직이지 않는다. 사전은 이미 당신의 브라우저 안에 있고, 검색은 당신 기기의 메모리를 상대로 일어난다.
이 구분이 전부다. 요청에 비밀번호가 안 실려 있기를 믿는 게 아니라, 요청 자체가 없는 것이다. 뭔가를 해석할 필요도, 코드를 이해할 필요도 없다. 올라가지 않는 숫자 하나만 보면 된다.
흠까지 포함한 전체 목록
논지가 “직접 확인해 보라”인 이상, 확인하면 나올 것을 숨기는 건 우스운 일이다. password.es의 정직한 목록은 이렇다.
분석 도구가 없다. 구글 애널리틱스도, 얌전한 대안 도구도, 픽셀도 없다. 하나 있었지만 전 페이지에서 걷어냈다. 쿠키가 없다. 사이트는 쿠키를 하나도 쓰지 않는다. 당신의 브라우저에 저장되는 게 하나 있기는 하다. 라이트 테마와 다크 테마 중 무엇을 선호하는지가 당신 기기의 로컬 스토리지에 남는다. 어디로도 전송되지 않고, 같은 개발자 도구에서 직접 지울 수 있다. 가입도, 계정도, 입력 양식도 없다. 데이터를 남기고 싶어도 남길 곳이 없다.
그리고 흠. 있으니까 말한다. 페이지가 로드될 때 구글 서버에 서체 두 개를 요청한다. 즉, 당신의 IP를 쓰는 누군가가 이 도메인의 페이지를 열었다는 사실을 구글이 본다는 뜻이다. 수많은 사이트가 그렇듯이. 당신이 입력하는 내용을 보는 건 아니다 — 그 요청은 그보다 먼저 일어나고, 다시 반복되지도 않는다. 하지만 제3자에게 나가는 요청은 요청이고, 바로 그 네트워크 탭에 뜰 것이다. 개발자 도구를 열어 보라고 해 놓고 이걸 말하지 않는다면 앞뒤가 맞지 않는다. 고칠 것 목록에 올라가 있다.
왜 이런 이야기를 하는가
“요청 0건, 완벽한 프라이버시”라고 써 놓고 태연히 넘어갈 수도 있었다. 더 멋있게 들렸을 테고, 세부에서는 거짓이었을 것이다. 사전 요청이 하나 있고, 서체가 있으니까. 누가 확인하면 무너지는 주장은 주장이 아니라 광고였던 것이다.
정확한 버전은 덜 매끈한 대신 검증을 견딘다. 당신이 입력하는 것은 당신의 브라우저 밖으로 나가지 않고, 그건 이미 설치돼 있는 도구로 30초면 눈에 보인다.
그리고 이건 당신에게 일반적인 기준으로 되돌아온다. 다음번에 어떤 사이트가 무슨 이유로든 비밀번호를 요구하거든, 그 탭을 열고 키를 누를 때 요청이 뜨는지 보시라. 뜬다면 당신의 비밀번호는 이미 떠난 것이다. 개인정보 처리방침에 뭐라고 적혀 있든, 입력 양식 옆 자물쇠 아이콘이 얼마나 예쁘든, 그 회사가 얼마나 번듯해 보이든 상관없다. 그리고 로딩 스피너가 한 바퀴 돌고 나서 분석 결과를 보여주는 사이트라면, 그동안 당신의 비밀번호가 어디를 다녀왔는지는 이미 아는 셈이다.
보안에서 가장 적게 이야기되는 규칙은 비밀번호에 관한 게 아니다. 검증에 관한 것이다. 믿는 건 좋다. 확인하는 게 더 좋고, 여기서는 공짜다.
출처: password.es 자체의 구조. 어느 브라우저의 개발자 도구로든 검증 가능하다 —
검사기는 입력칸에 포커스가 갈 때 자체 도메인에서 목록이 포함된 분석
라이브러리(zxcvbn-ko.min.js)를 내려받고, 타이핑하는 동안에는 아무 요청도 보내지
않는다 · crypto.getRandomValues(), 암호학적 난수를 위한 Web Crypto API의 표준
API · 사이트 코드에는 분석 도구도, 픽셀도, 쿠키도 없으며 로컬에 저장되는 유일한
데이터는 테마 설정이다 · 서체는 Google Fonts에서 제공된다.