인증의 모든 방식은 하나의 원리로 정리됩니다. 비밀은 보내지 않고, 비밀로 계산한 결과만 보낸다. 내가 브라우저가 되어 은행과 연결하는 장면을 따라가며, 숫자가 실제로 어떻게 오가는지 직접 바꿔 봅니다. 처음 보는 사람도 위에서부터 순서대로 읽으면 끝까지 이해할 수 있게 구성했습니다.
인증(AuthN)은 "너 누구야"이고, 인가(AuthZ)는 "너 뭐 할 수 있어"입니다. 로그인은 인증, 관리자 메뉴가 보이는지는 인가입니다. 이 페이지는 인증을 다룹니다.
어떤 인증이든 두 단계로 나뉩니다. 처음 증명(누구인지 확인)과 이후 유지(매번 "아까 그 상대 맞다"를 이어 가기). 은행 사이트에 들어가면 이 과정이 양쪽 방향으로 한 번씩 일어납니다.
TLS가 끝나도 은행은 아직 내가 누군지 모릅니다. "누군가와 안전한 통로가 생겼다"까지만 압니다. 그래서 그 안에서 다시 로그인을 합니다.
아래 장면과 실험은 모두 이 부품들의 조합입니다. 지금 다 외울 필요는 없고, 읽다가 막히면 돌아와서 보면 됩니다.
입력을 넣으면 고정 길이 값이 나오는 한 방향 계산. 같은 입력은 항상 같은 값, 결과에서 입력을 되찾을 수 없음.
비유: 지문. 지문으로 사람은 확인해도, 지문에서 사람을 만들 수는 없다.
쓰이는 곳: 비밀번호 저장, 장비 로그인 응답값
비밀값 + 내용을 섞어 만든 해시. 같은 비밀을 가진 쪽만 같은 결과를 만들 수 있음.
비유: 눈앞에서 금고를 열어 보이기. 비밀번호를 말하지 않고도 안다는 걸 증명.
쓰이는 곳: TLS 확인 메시지, OTP, 서명된 토큰
잠그는 키와 여는 키가 같은 키. 빠르지만, 상대와 미리 나눠 가져야 하는 문제가 있음.
비유: 집 열쇠 복사본. 둘 다 같은 열쇠를 들고 있어야 함.
쓰이는 곳: TLS 연결 후 모든 데이터 암호화 (아래의 키 2)
대칭키를 보내지 않고 양쪽이 각자 계산해서 같은 키를 얻는 방법. 공개된 재료만 오감.
비유: 각자 비밀 물감을 섞어 교환한 뒤 다시 섞으면 같은 색이 나옴.
쓰이는 곳: TLS 1.3 (실험 1)
한 쌍으로 만들어지는 키. 개인키는 주인만 갖고, 공개키는 누구에게나 줌. 이 페이지에서는 서명용으로만 씀.
비유: 도장(개인키)과 인감 대조용 견본(공개키).
쓰이는 곳: TLS 서명②, 인증서
개인키로 만들고 공개키로 확인. "이 내용은 키 주인이 이 모양 그대로 만들었다"를 보증. 못 고치게 할 뿐, 못 보게 하는 건 아님.
비유: 계약서 도장. 내용은 누구나 읽지만 고치면 도장이 안 맞음.
쓰이는 곳: 인증서, TLS 서명②, JWT
인증서 = 공개키 + 이름(도메인) + 유효기간 + CA의 서명. CA는 인증서를 발급하는 기관이고, CA 공개키는 브라우저·OS에 미리 들어 있음.
비유: 신분증. 사진은 공개키, 발급기관 도장은 CA 서명.
쓰이는 곳: TLS (장면)
number used once. 이번 한 번만 쓰는 값. 대부분 난수로 만들지만 순번(1, 2, 3…)이어도 됨. 공개해도 됨.
비유: 창구 직원이 "방금 부른 숫자 7392를 적고 지금 서명하세요".
쓰이는 곳: TLS 랜덤값, 장비 로그인, OTP의 시각 (실험 2)
서명은 내용을 숨기지 않습니다. 고치면 들킨다는 보증입니다. 숨기는 건 암호화(대칭키)의 일입니다.
그런 용도도 있지만(옛 RSA 키 교환), TLS 1.3에서 은행 키 쌍은 서명에만 씁니다. 개인키로 서명을 만들고, 공개키로 확인합니다. 데이터를 잠그는 건 키 교환으로 만든 대칭키입니다.
"시작"을 누르면 숫자가 실제로 오가는 모습을 보여 줍니다. 신원 확인은 은행의 고정 키로, 암호화 키는 매번 새로 각자 계산합니다. 아래 "선 위에 흐른 것" 칸을 보면 엿보는 사람이 무엇을 손에 넣는지 알 수 있습니다.
나는 비밀숫자 6을, 은행은 비밀숫자 15와 은행 개인키를 가지고 있습니다. 둘 다 아직 한 번도 밖으로 내보내지 않았습니다. 양쪽은 이번 연결에만 쓰는 무작위 값(nonce) R1, R2도 하나씩 만듭니다.
| 신분증 | 인증서 |
|---|---|
| 얼굴 사진 | 은행 공개키 |
| 이름, 주소 | 도메인(bank.com), 유효기간 |
| 발급기관 도장 | CA 서명 ① |
| 앞사람 얼굴과 사진 대조 | 은행 공개키로 서명 ② 확인 |
| 서명 ① | 서명 ② | |
|---|---|---|
| 어디에 | 인증서 | 지금 이 대화 전체 |
| 누가 찍나 | CA | 은행 (개인키) |
| 뭘로 확인 | CA 공개키 (브라우저 내장) | 은행 공개키 (인증서 안) |
| 의미 | 이 공개키는 진짜 은행 것 | 인증서를 주운 게 아니라 진짜 본인 |
확인 순서 한 줄: CA 공개키 → 인증서 확인 → 그 안의 은행 공개키 꺼냄 → 은행 공개키 → 서명② 확인.
비밀숫자 6은 끝까지 나가지 않습니다. 나가는 것은 6으로 계산한 재료 8뿐입니다.
반대입니다. 인증서는 브라우저에 내장된 CA 공개키로 확인하고, 은행 공개키로 확인하는 것은 은행이 그 자리에서 찍은 서명②입니다.
인증서 안에 들어 있습니다. 인증서를 받으면 같이 받는 것입니다.
인증서는 공개 정보라 누구나 복사해 보낼 수 있습니다. 서명②는 은행 개인키가 있어야만 만들 수 있어서, 인증서를 주운 사람과 진짜 은행을 가려 줍니다.
돌려보내지 않습니다. 8을 포함한 대화 내용에 서명만 합니다. 나는 내가 보낸 8을 알고 있으니, 서명 내용과 대조할 수 있습니다.
8은 그냥 보냅니다. 일반 브라우저는 자기 인증서가 없습니다. 대신 은행의 서명②가 "받은 값은 8"까지 포함하므로, 해커가 바꾸면 내가 서명을 확인할 때 걸립니다.
절대 보내지 않습니다. 대화 요약과 키 2로 계산한 결과값(HMAC)만 보냅니다. 금고 비밀번호를 말해 주는 게 아니라 눈앞에서 금고를 열어 보이는 것입니다. 실제로는 2를 그대로 쓰지 않고, 2에서 용도별 키(확인용, 암호화용, 방향별)를 여러 개 뽑아 씁니다.
이것은 TLS 1.2까지의 RSA 키 교환입니다. 통신을 녹화해 두었다가 나중에 은행 개인키를 훔치면 과거 통신이 전부 풀려서 TLS 1.3에서 없앴습니다. 1.3에서 은행 키는 서명에만 씁니다. (비교 보기)
있습니다. 양쪽이 32바이트 랜덤(R1, R2)을 하나씩 보내고, 매번 새로 만드는 재료(8, 19) 자체도 일회용입니다. 서명②가 이 값들을 포함한 대화 전체에 찍히기 때문에, 녹화한 옛 응답은 오늘 쓸 수 없습니다. 연결 뒤 데이터 덩어리에도 순번이 섞인 nonce가 붙습니다.
하지 않습니다. 은행이 아는 것은 "누군가와 안전한 통로가 생겼다"까지입니다. 그래서 그 통로 안에서 로그인을 합니다. (로그인과 세션)
위 장면의 8, 19, 2가 어디서 나왔는지 직접 바꿔 봅니다. 공개값은 밑수 5, 나눗수 23이고, 계산은 "5를 n번 곱해 23으로 나눈 나머지"입니다. 공개값은 누구나 압니다.
| 숫자 | 정체 | 밖으로 나가나 |
|---|---|---|
| 5, 23 | 공개값 (계산 규칙) | 누구나 앎 |
| 6, 15 | 비밀숫자 | 절대 안 보냄 |
| 8, 19 | 재료 (상대가 비밀숫자로 계산해 보낸 값) | 이것만 교환 |
| 2 | 최종 키 | 각자 계산, 안 보냄 |
키 교환은 엿보기를 막고, 서명은 끼어들기를 막습니다. 둘은 역할이 다르고, 둘 다 있어야 안전합니다.
곱하는 순서만 다를 뿐 둘 다 5를 (내 비밀 × 은행 비밀)번 곱한 것입니다. 곱셈은 순서를 바꿔도 같고, 중간에 23으로 나눈 나머지를 취해도 이 성질은 유지됩니다.
5ⁿ mod 23을 n = 1부터 넣어 보면:
이 예시는 숫자가 작아서 대입하면 찾힙니다. 하지만 결과가 5, 2, 10, 4, 20…처럼 규칙 없이 튀어서 "크면 줄이고 작으면 늘리는" 지름길이 없습니다(이산 로그 문제). 실제 비밀숫자는 77자리 수준이라, 최선의 공격으로도 우주 나이의 수백 배가 걸립니다. 반대로 정방향 계산은 77자리여도 순식간입니다.
덧셈은 거꾸로 계산이 쉬워서, 엿보는 사람이 재료로 비밀을 되찾거나 같은 키를 만들어 버립니다. 정방향은 쉽고 역방향은 사실상 불가능한 연산이 필요합니다.
계산 규칙을 알아도 거꾸로 가는 지름길이 없습니다. 하나씩 대입하는 수밖에 없는데, 실제 크기에서는 대입할 개수가 상상을 넘습니다. 양자컴퓨터는 이 문제를 빠르게 풀 수 있어서, 대비용 양자내성암호(PQC)가 TLS에 섞여 들어가는 중입니다.
받는 것은 은행이 비밀숫자 15로 계산한 재료 19입니다. 15는 은행 밖으로 나오지 않습니다.
nonce는 "이번 한 번만 쓰고 버리는 값"입니다. 이 값을 계산에 섞으면, 엿들은 결과를 나중에 다시 보내는(재전송) 공격이 통하지 않습니다. TLS의 랜덤값, OTP의 현재 시각, 장비 로그인의 장비 난수가 모두 같은 역할입니다.
| 비밀숫자 (6) | nonce | |
|---|---|---|
| 만드는 법 | 무작위 | 무작위 또는 순번 |
| 공개 | 숨김 | 그냥 보냄 |
| 목적 | 키 만들기 | 이번 대화를 유일하게 |
| 털리면 | 키가 털림 | 상관없음 |
서명이나 HMAC과 같이 쓰일 때 의미가 생깁니다.
창구 직원이 "미리 써 온 서명 말고, 제가 방금 부른 숫자 7392를 적고 지금 서명하세요"라고 하는 것과 같습니다. 숫자가 남에게 들려도 상관없습니다. 매번 새로 나오기 때문에 미리 준비한 서명으로는 못 맞춥니다.
아래는 장비(카메라) 로그인을 흉내 낸 실험입니다. 비밀번호 원문은 한 번도 선 위로 나가지 않습니다.
장비 nonce는 장비가 먼저 보내 줍니다(첫 줄). 장비가 정했으니 장비는 이미 알고 있어 내가 다시 보낼 필요가 없습니다. 이 값 덕분에 해커가 예전 로그인을 녹화해 다시 보내도 장비가 속지 않습니다.
내 nonce는 내가 만든 값이라 장비는 모릅니다. 그래서 같이 보내야 장비도 똑같이 계산할 수 있습니다. 결과가 장비 혼자 정한 값에만 달려 있지 않게 해서, 가짜 장비가 같은 nonce를 반복해 내놓고 내 응답을 모아 두는 수법도 막습니다.
응답값(digest)은 비밀번호와 두 nonce를 섞어 계산한 값입니다. 비밀번호를 아는 쪽만 같은 값을 만들 수 있고, 이 값에서 비밀번호를 되찾을 수는 없습니다. 두 nonce 중 하나만 바뀌어도 완전히 다른 값이 됩니다.
예시 비밀번호는 실제 값이 아닙니다. nonce가 매번 바뀌니 녹화해 둔 값은 다음 연결에서 통하지 않습니다.
반만 맞습니다. 대부분 난수로 만들지만, 정의는 "무작위"가 아니라 "한 번만 쓴다"입니다. TLS 연결 뒤 데이터 덩어리의 nonce는 순번으로 만듭니다. 겹치지만 않으면 됩니다.
공개해도 됩니다. nonce는 비밀이 아니라 "이번 한 번"을 표시하는 값입니다. 비밀은 비밀번호나 비밀숫자 쪽이 맡습니다.
SSL(Secure Sockets Layer)이 원조이고, 버전업하며 이름이 TLS(Transport Layer Security)로 바뀐 같은 계보입니다. "SSL 인증서"는 습관적인 이름일 뿐 실제로는 전부 TLS이고, 인증서 자체는 버전과 상관없습니다.
| 버전 | 시기 | 상태 |
|---|---|---|
| SSL 2.0, 3.0 | 1990년대 | 폐기 (보안 결함) |
| TLS 1.0, 1.1 | 1999, 2006 | 폐기. 주요 브라우저가 2020년에 지원 중단 |
| TLS 1.2 | 2008 | 아직 많이 씀. 설정만 잘하면 안전 |
| TLS 1.3 | 2018 | 현재 표준 (이 페이지의 장면) |
내가 임시키를 만들어 은행 공개키로 잠가 보냄 → 은행이 개인키로 풂
재료만 교환하고 각자 계산. 비밀숫자는 끝나면 버림. 은행 키는 서명에만 씀
녹화해 둔 과거 통신을 나중에 못 푸는 성질을 전방 비밀성(Forward Secrecy)이라고 합니다. TLS 1.3이 RSA 키 교환을 없앤 이유입니다.
| 1.2에서 고를 수 있는 것 | 상태 |
|---|---|
| ECDHE 키 교환 (1.3과 같은 원리) | 안전 |
| RSA 키 교환 | 전방 비밀성 없음 |
| AES-GCM, ChaCha20 암호화 | 안전 |
| CBC 등 옛 암호화 방식 | 과거 공격 사례 다수 |
1.3은 위험한 옵션을 아예 지운 버전입니다. 1.2를 계속 쓰는 이유는 호환성입니다. 오래된 OS, 사내 시스템, 방화벽·프록시 같은 중간 장비가 1.3을 못 알아듣는 경우가 남아 있습니다.
최신 브라우저는 연결 자체를 거부합니다. 하지만 펌웨어 업데이트가 끊긴 오래된 카메라, NVR, 산업용 장비는 아직 1.0을 쓰기도 합니다. 오래된 장비 웹 화면이 최신 브라우저에서 안 열리는 이유가 대부분 이것이고, 그런 장비가 남아 있다면 그 자체가 보안 구멍입니다.
TLS로 안전한 통로가 생긴 뒤, 그 안에서 일어나는 일입니다. 핵심은 번호표와 서버 장부입니다.
번호표 자체에는 아무 정보가 없습니다. 의미는 전부 서버 장부에 있습니다. 그래서 "서버가 기억하는 방식"이라고 부릅니다.
| 번호표 | 누구 | 만료 |
|---|
| 공격 | 어떻게 | 방어 |
|---|---|---|
| XSS | 게시글 등에 심은 악성 스크립트가 내 브라우저에서 실행되어, 이미 풀린 쿠키를 읽어 감 | HttpOnly |
| HTTP 노출 | http:// 로 유도해 암호화 없는 요청에 쿠키가 실림 | Secure |
| CSRF | 훔치지 않고 악용. 해커 사이트가 내 브라우저로 은행에 요청을 보내면 쿠키가 자동으로 붙음 | SameSite, CSRF 토큰 |
| 악성코드 | PC 자체가 감염되어 저장소를 통째로 가져감 | 짧은 세션 만료 |
TLS는 현금 수송차입니다. 운송 중에는 안전하지만, 집에 도둑이 들거나 누가 나인 척 심부름을 보내는 건 못 막습니다. 쿠키 옵션은 집 보안입니다.
통로 안은 안전합니다. 털리는 곳은 통로 양 끝(브라우저 안, 기기 자체)과 통로가 없는 순간(http)입니다. 위 표가 그 목록입니다.
안전해지는 진짜 이유는 서버 장부에서 지워지기 때문입니다. 해커가 미리 복사해 간 번호표도 장부에 없으니 무효가 됩니다. 위 실험에서 훔친 뒤 로그아웃하고 다시 해커 버튼을 눌러 보세요.
세션은 번호표에 정보가 없고 서버 장부에 다 있었습니다. JWT는 반대로 필요한 정보를 토큰 안에 적고 서버가 서명해서 줍니다. 서버는 장부 없이 서명만 확인하고 내용을 믿습니다. TLS의 서명과 같은 원리입니다.
내용을 한 글자 고쳐 보세요. 예를 들어 viewer를 admin으로.
| 세션 | JWT | |
|---|---|---|
| 정보 위치 | 서버 장부 | 토큰 안 |
| 서버 확인 | 장부 조회 | 서명 검증 |
| 강제 로그아웃 | 장부에서 지우면 즉시 | 만료 전까지 못 막음 |
| 서버 여러 대 | 장부 공유 필요 | 서명 키만 있으면 됨 |
| 잘 맞는 곳 | 웹 사이트 하나 | API, 모바일 앱, 여러 서비스 |
장부가 없으니 "그 사람 토큰만" 골라 무효로 할 수 없습니다.
| 방법 | 효과 | 대가 |
|---|---|---|
| 서명 키 교체 | 즉시 전원 차단 | 모두 재로그인 |
| 짧은 만료 + 리프레시 폐기 | 5~15분 뒤 차단 | 그 사이엔 못 막음 |
| 차단 목록 | 즉시 그 사람만 | 매 요청 조회 = 장부 부활 |
판단 기준: 사이트 하나 + 브라우저 사용자이고, 권한 회수가 즉시 되어야 하는 관제 시스템이라면 세션이 구조적으로 맞습니다. "요즘은 JWT"라는 이유만으로 바꾸면 오히려 손해일 수 있습니다.
인코딩일 뿐이라 누구나 열어 볼 수 있습니다. 서명은 못 고치게 하는 것이지 못 보게 하는 것이 아닙니다. 비밀번호 같은 값은 절대 넣으면 안 됩니다.
모든 토큰이 같은 키로 서명되었으니 전원의 토큰이 무효가 됩니다. 최후의 수단입니다.
보통 수명이 짧은 액세스 토큰과 수명이 긴 리프레시 토큰을 함께 쓰는데, 리프레시 토큰은 서버가 목록을 관리해 폐기할 수 있게 합니다. 결국 일부는 장부를 다시 둡니다.
비밀번호가 털려도 막으려고, "그 폰을 가지고 있는지"를 한 번 더 봅니다. 구글 OTP 같은 앱은 등록 때 QR로 나눠 가진 비밀값에 현재 시각(30초 단위)을 섞어 6자리를 만듭니다. 시각이 nonce 역할을 합니다. 아래는 표준 방식(TOTP)으로 실제 계산합니다.
| TLS 확인 메시지 | OTP | |
|---|---|---|
| 비밀 | 키 2 | 등록 때 받은 비밀값 |
| 비밀 얻는 법 | 매번 새로 키 교환으로 계산 | 미리 한 번 나눠 가짐 |
| 섞는 재료 (nonce 역할) | 대화 내용 | 현재 시각 |
| 확인 대상 | 같은 키를 가졌나 | 등록한 그 폰을 가졌나 |
| 역할 | 통로 확인 | 사용자 인증 (2단계) |
공통 원리는 하나입니다. 비밀은 안 보내고, 비밀로 계산한 결과만 보냅니다. 차이는 비밀을 언제 얻느냐입니다.
wvp.idisman.co.kr는 위의 방식들을 자리마다 나눠 씁니다. 사람의 로그인, 영상 서버 접속, 장비 연결이 각각 다른 방식입니다.
| 구간 | 방식 | 이 페이지의 어느 장 |
|---|---|---|
| 브라우저 ↔ WVP | TLS (인증서 확인) | 2. TLS 1.3 한 바퀴 |
| 사이트 로그인 | Django 세션. sessionid 쿠키 = 번호표, django_session 테이블 = 장부, csrftoken = CSRF 방어 | 6. 로그인과 세션 |
| 영상 서버 접속 | 서명된 짧은 토큰. 짧게 만료시켜 "즉시 로그아웃이 안 되는" 약점을 줄임 | 7. 서명된 토큰 |
| WVP ↔ 장비 | DirectIP: nonce + 응답값 로그인 (아래 대응표) | 4. nonce |
로그인 주소 /login/?next=%2F는 Django 기본 패턴으로, "로그인이 끝나면 원래 가려던 / 로 보내 달라"는 뜻입니다. 사람 계정은 즉시 차단이 중요해서 세션이 맞고, 영상 서버는 매 요청 장부를 조회하지 않고 빠르게 검증하는 게 유리해서 짧은 토큰이 맞습니다.
| 순서 | 실제로 오가는 것 | 이 페이지의 어느 것 |
|---|---|---|
| 1 | TCP 접속 → 인사(hello) | 아직 인증 없음 |
| 2 | 장비가 장비 정보 + nonce를 보냄 | 실험 2의 장비 nonce |
| 3 | (새 펌웨어) 같은 소켓을 TLS 1.3으로 감쌈. 인증서 확인은 하지 않음 | 장면에서 3번(인증서·서명② 확인)이 빠진 상태: 엿보기는 막고 끼어들기는 못 막음 |
| 4 | 로그인: 비밀번호 대신 계산값을 보냄 | 실험 2 그대로 |
| 5 | 장비가 성공/실패를 답함 | 장비의 대조 |
| 6 | 라이브·재생. 번호표 없이 연결 자체가 세션 | 세션과 비슷하지만, 끊기면 처음부터 |
대상이 사람에서 기기로 바뀌었을 뿐, "처음 증명"과 "이후 유지"라는 구조는 같습니다.
| 웹 로그인 | 카메라 ↔ NVR | |
|---|---|---|
| 누가 | 사람 → 서버 | 기기 → 기기 |
| 처음 증명 | ID/PW 입력 | 장비 계정, 등록(페어링) |
| 이후 유지 | 세션 쿠키, 토큰 | 연결 유지 또는 매 요청 재인증 |
| 대표 방식 | 세션, JWT, OAuth | HTTP Digest(RTSP·ONVIF), nonce 응답값, mTLS |
| 대표 약점 | 비밀번호 유출, 번호표 탈취 | 기본 비밀번호, 인증서 확인 없는 통로 |
mTLS는 브라우저처럼 한쪽만 확인하는 게 아니라 양쪽이 서로 인증서와 서명②를 주고받아 확인하는 방식입니다. 사람보다 기기 간 통신에 주로 씁니다. DirectIP 3단계에 이것이 들어가면 끼어들기까지 막을 수 있습니다.