[논문 리뷰] WireGuard 논문으로 이해하는 VPN 프로토콜 설계
WireGuard는 2015년에 처음 발표된 VPN 프로토콜입니다. 이전에도 OpenVPN이나 IPsec 같은 VPN 프로토콜이 존재했지만, WireGuard는 현대적인 암호화 기법을 활용해 더 간결한 코드베이스와 높은 성능을 달성했습니다. 또한 Linux 5.6에 커널 모듈로 통합되면서 점점 더 많은 곳에서 채택되고 있습니다.
WireGuard라는 이름이 낯선 분들도 계실 수 있지만, 이미 자신도 모르는 사이에 사용하고 있을 수 있습니다. 예를 들어 Tailscale, Cloudflare WARP 같은 서비스가 WireGuard를 기반으로 구축되어 있습니다.
저도 openclaw에서 사용하는 네트워크 플랫폼인 Tailscale이 WireGuard 기반이라는 것을 알게 되면서 관심을 갖게 되었습니다. 홈서버를 외부에 노출해 openclaw를 어디서든 사용하고 싶었지만, 동시에 저만 접근할 수 있어야 했습니다. WireGuard는 인증되지 않은 패킷에는 아예 응답하지 않기 때문에, 외부 스캐너에게 서버의 존재 자체를 숨길 수 있습니다.
이 글에서는 WireGuard의 whitepaper(NDSS 2017)를 바탕으로, VPN과 네트워크에 익숙하지 않은 분들도 이해할 수 있도록 WireGuard가 어떻게 안전한 통신을 구현하는지 설명합니다.
Prerequisite: VPN
WireGuard를 이해하려면 VPN에 대한 이해가 먼저 필요합니다. 간단하게 짚고 넘어가겠습니다.
VPN(Virtual Private Network)은 물리적으로 떨어진 두 기기를 마치 같은 내부 네트워크에 있는 것처럼 연결하는 기술입니다. Public 인터넷을 경유하되, 암호화된 터널로 감싸서 중간 노드(카페 WiFi, ISP, 라우터 등)가 트래픽을 볼 수 없게 합니다.
[노트북] ──── public 인터넷 ────→ [웹 서버]
│
도청 가능 구간
VPN 사용:
[노트북] ════ 암호화된 터널 ════ [VPN 서버] ──── 인터넷 ────→ [웹 서버]
│ │
안전 평문
(도청해도 내용 못 봄) (신뢰할 수 있는 네트워크 - 내부 인터넷 등)VPN 서버는 내가 신뢰하는 네트워크의 입구입니다.
대표적인 use case
- Eavesdrop 방지: 카페 WiFi처럼 신뢰할 수 없는 네트워크에서 통신을 encrypt
- 원격 접속: 집에서 회사 내부 서버(10.0.0.x)에 접근. Private IP는 public 인터넷에서 라우팅되지 않으므로, VPN 터널 안에서 사내 IP로 라우팅
VPN에 연결하면 내 노트북에 virtual network interface(`tun0`)가 생기고 사내 IP가 할당됩니다. 물리 인터페이스(`eth0`)는 그대로 유지되며, routing table이 사내 대역 트래픽만 `tun0`으로 보내도록 수정됩니다.
인터페이스:
eth0: 192.168.1.100 ← 집 공유기 IP. VPN 서버와의 실제 통신에 사용
tun0: 10.0.0.2 ← VPN이 만든 가상 인터페이스. 사내 IP
라우팅 테이블:
10.0.0.0/24 → tun0 (사내 대역은 터널로)
0.0.0.0/0 → eth0 (나머지는 원래대로)
패킷 흐름 (10.0.0.5에 접속 시):
앱 → tun0 → VPN 클라이언트가 암호화 → eth0 → VPN 서버가 복호화 → 사내 10.0.0.5Prerequisite: Cryptography 기초
WireGuard의 handshake를 이해하는 데 필요한 cryptography 개념들을 정리합니다.
Symmetric Encryption과 AEAD
하나의 key로 encrypt/decrypt하는 것이 symmetric encryption입니다. 빠르지만 양쪽이 같은 key를 미리 공유해야 합니다.
WireGuard는 단순한 symmetric encryption 대신 AEAD(Authenticated Encryption with Associated Data)를 사용합니다. Encryption과 integrity 검증을 한 번에 수행합니다.
(key, nonce, plaintext, associated data) → (ciphertext, authentication tag)Decrypt 시 authentication tag가 일치하지 않으면 데이터가 변조되었거나 key가 틀린 것이므로 전체를 거부합니다. Associated data는 encrypt하지 않지만 변조 방지는 필요한 데이터(예: 패킷 헤더)에 사용됩니다.
Diffie-Hellman Key Exchange
양쪽이 비밀을 직접 전송하지 않고도 shared secret을 만드는 알고리즘입니다.
사전 합의: 소수 p, 생성자 g (둘 다 공개)
Alice Bob
비공개키 a (랜덤 정수) 비공개키 b (랜덤 정수)
공개키 A = g^a mod p 공개키 B = g^b mod p
──── A를 전송 ──────────────────→
←──────────────────── B를 전송 ────
공유 비밀 = B^a mod p 공유 비밀 = A^b mod p
= g^(ba) mod p = g^(ab) mod p
↑ 동일한 값! ↑ 동일한 값!Eavesdropper가 g, p, A, B를 모두 보더라도, a나 b를 역산하는 것은 현실적으로 불가능합니다.
WireGuard는 Curve25519 (Elliptic Curve DH) 사용합니다.
WireGuard가 풀려고 하는 문제
Whitepaper의 첫 문장이 WireGuard의 목표를 잘 요약하고 있습니다.
WireGuard is a secure network tunnel, operating at layer 3, implemented as a kernel virtual network interface for Linux, which aims to replace both IPsec for most use cases, as well as popular user space and/or TLS-based solutions like OpenVPN, while being more secure, more performant, and easier to use
기존 VPN은 수십 년간 발전하면서 복잡해졌습니다. IPsec은 관련 RFC만 수십 개이고, OpenVPN은 10만 줄 이상의 코드로 이루어져 있습니다. 코드가 크고 설정이 복잡하면 그만큼 취약점이 생기기 쉽습니다.
WireGuard의 목표는 SSH처럼 단순한 VPN입니다. SSH가 원격 접속의 사실상 표준이 된 것처럼, 설정이 쉽고 코드가 작으면서도 보안이 강한 VPN을 만들겠다는 것입니다.
기존 VPN들의 구체적인 문제들은 다음과 같습니다.
- IPsec: Linux의 xfrm layer(ciphersuite 및 transformation 결정)와 복잡한 IKEv2 프로토콜(키 교환)에 의존합니다. transport encryption layer와 key exchange layer가 분리되어 있어 사용자 입장에서 설정과 관리가 복잡합니다.
- OpenVPN: TLS 기반의 user space 솔루션입니다. IPsec에 비해 설정이 간단합니다. 핵심 아이디어는 TLS 라이브러리(OpenSSL) 위에 VPN을 구축하는 것입니다(TLS handshake로 key를 교환하고 별도의 채널로 암호화). 하지만 커널과 user space 간 패킷 복사가 빈번해 성능이 떨어지고, full TLS stack에 의존하기 때문에 attack surface가 넓습니다.
WireGuard는 이 문제들을 다음과 같이 해결합니다.
- Virtual interface: `wg0` 같은 virtual interface를 제공하여 `ip`나 `ifconfig` 같은 standard tool로 제어할 수 있습니다.
논문에서는 SSH를 비유로 드는데, SSH에서 public key를 등록하면 해당 사용자만 접속할 수 있듯, WireGuard에서 peer의 public key와 allowed IP를 등록하면 해당 peer만 해당 IP 범위로 통신할 수 있습니다.
ip link add dev wg0 type wireguard
ip address add 10.0.0.1/24 dev wg0
wg set wg0 private-key ./privatekey \
peer ABCDEF... allowed-ips 10.0.0.2/32 endpoint 203.0.113.5:51820
ip link set wg0 up- 제한된 cryptographic primitive: Curve25519, ChaCha20-Poly1305 등으로 사용 가능한 프로토콜을 고정하여, cipher negotiation 과정에서 발생할 수 있는 취약점을 차단했습니다.
- Key distribution agnostic: OpenSSH처럼 키 배포 방식에 agnostic합니다. PGP-signed email을 쓰든 LDAP을 쓰든, 어떤 인프라 위에서도 동작합니다.
- Layer 3 동작: 커널 내에서 Layer 3로 동작하여 높은 성능 유지 및 다양한 네트워크 환경에서 사용할 수 있습니다.
CryptoKey Routing
Wireguard에서 각 peer는 public key(Curve25519)와 allowed IPs를 가지며, 이 매핑이 곧 라우팅 테이블이 됩니다.

패킷의 방향에 따라 동작이 다릅니다.
- Outgoing: 패킷이 `wg0`으로 전송되면, destination IP에 매핑된 peer를 찾고 해당 peer와의 secure session으로 encrypt합니다.
- Incoming: encrypted 패킷을 decrypt한 뒤, inner packet의 source IP가 해당 peer의 allowed IPs에 포함되는지 확인합니다. 없으면 drop합니다.
패킷 캡슐화
WireGuard는 Layer 3 터널입니다. Inner packet(internal source IP를 가진 원본 패킷)을 UDP 패킷의 payload로 캡슐화합니다. 외부 UDP 헤더에는 실제 public IP(Internet endpoint)가, 내부에는 `wg0`에서만 유효한 internal IP가 들어갑니다.

roaming
WireGuard는 peer의 Internet endpoint를 라우팅 테이블에 유지합니다. 패킷을 수신하면 outer IP에서 peer의 현재 위치를, inner source IP에서 peer의 identity를 파악합니다.
이전 그림에서 호스트 `gN65...z6EA`가 `HIgo...f8yk`에게 `192.95.5.69:41414`로 패킷을 보내면, `HIgo...f8yk`는 decrypt 후 inner source IP로 peer identity를 확인하고 `gN65...z6EA`의 Internet endpoint를 업데이트합니다.
이 덕분에 WiFi에서 모바일 네트워크로 전환하는 등 Internet endpoint가 바뀌어도, identity는 internal IP로 식별되므로 연결이 자연스럽게 유지됩니다. 수신 측이 새로운 endpoint를 학습하면 양방향 통신이 다시 가능해집니다.
예시를 하나 봅시다.

wg0이 10.192.122.3/24로 등록이 되고 `ip route add 10.0.0.0/8 dev wg0`으로 10.0.0.0/8 IP range도 wg0을 사용합니다.
이때 유저가 10.10.10.230으로 패킷을 보내면 해당 패킷은 wg0 인터페이스를 사용하고, public key gN65...z6EA 의 secure session으로 암호화되어 192.95.5.70:54421로 보내지는 UDP 패킷에 첨부됩니다.
Protocol & Cryptography
암호화된 encapsulated 패킷을 보내려면 먼저 1-RTT handshake로 키를 교환해야 합니다. Handshake가 완료되면 양측은 공유된 symmetric key로 데이터를 암호화합니다.
WireGuard 프로토콜이 달성하려는 보안 속성은 다음과 같습니다.
- Authenticated Key Exchange(AKE): 두 peer가 서로의 identity를 확인하는 동시에 shared secret을 생성합니다. WireGuard는 Noise "IK" 패턴을 사용해 1-RTT handshake로 이를 달성합니다.
- Perfect Forward Secrecy(PFS): peer의 long-term static private key가 유출되어도 과거 트래픽을 decrypt할 수 없습니다. Handshake마다 ephemeral key pair를 새로 생성하여 달성합니다.
- Identity Hiding: passive observer가 peer의 static public key를 알아낼 수 없습니다. 첫 번째 handshake 메시지에서 static key를 암호화하여 달성합니다.
- Replay Protection: 공격자가 handshake 패킷을 캡처해 재전송하더라도 세션을 탈취할 수 없습니다. 메시지에 암호화된 timestamp를 포함하고, responder는 기존에 받은 가장 높은 timestamp 이하의 메시지를 drop합니다.
또한 WireGuard는 인증되지 않은 패킷에 절대 응답하지 않아, 공격자에게 자신의 존재를 노출하지 않습니다.
기존 VPN 솔루션에서 이것이 어려웠던 이유는 handshake 구조에 있습니다. 여러 RTT가 필요한 프로토콜은 인증이 완료되기 전에 응답을 보내야 하므로, 그 자체로 서버의 존재가 드러납니다.
WireGuard는 첫 패킷만으로 인증을 수행해 이 문제를 해결하지만, 이 방식은 replay attack에 취약해지는 트레이드오프가 있습니다.
WireGuard는 이 트레이드오프를 (1) replay attack 방지와 (2) DoS 완화 메커니즘으로 보완합니다. 이 두 가지를 먼저 설명한 뒤, 핵심인 handshake 과정을 설명하겠습니다.
Replay Attack 방지
Initiator는 첫 번째 메시지에 TAI64N timestamp을 암호화해 포함합니다. Responder는 peer마다 수신한 가장 높은 timestamp를 기록하고, 그 이하의 timestamp를 가진 메시지는 drop합니다.
Responder가 재시작하여 timestamp 상태를 잃어도 큰 문제가 되지 않습니다. Replay attack의 목적은 진행 중인 secure session의 상태를 망치는 것인데, 재시작 직후에는 진행 중인 세션이 없기 때문입니다. 이후 정상적인 initiator가 attacker보다 최신 timestamp로 handshake를 시도하면 정상 세션이 수립되고, attacker의 replay는 오래된 timestamp로 인해 무효화됩니다.
DoS 완화
WireGuard의 handshake는 Curve25519 point multiplication을 수행하는데, 이 연산은 CPU intensive합니다. 공격자가 handshake 요청을 대량으로 보내면 responder의 CPU를 소진시킬 수 있습니다.
WireGuard는 cookie 메커니즘으로 이를 완화합니다. Responder의 CPU 부하가 높으면, handshake를 진행하는 대신 initiator에게 암호화된 cookie를 보냅니다. Initiator는 이 cookie의 MAC을 handshake 메시지에 담아 재전송합니다.
Responder는 비싼 DH 연산 없이 MAC만 검증하면 되므로 CPU 부담이 크게 줄어듭니다. MAC이 initiator의 source IP와 port에 바인딩되어 있어 rate limiting 적용도 용이합니다.
WireGuard의 cookie 메커니즘이 기존 방식과 다른 점은 다음과 같습니다.
- Silent until authenticated: 기존 cookie 메커니즘은 누구에게나 cookie를 응답하지만, WireGuard는 initiator가 responder의 public key로 계산한 MAC을 포함한 경우에만 cookie를 응답합니다. 따라서 스캐너에게 존재를 드러내지 않습니다.
- Cookie 암호화: cookie를 responder의 public key로 암호화해 전송하여 man-in-the-middle 공격으로부터 보호합니다.
- Binding 강화: cookie를 메시지에 bind하는 과정에 추가 정보를 포함하여, 공격자가 가짜 cookie로 정상 연결을 방해하는 것을 방지합니다.
Noise
WireGuard의 handshake는 직접 설계한 것이 아니라, Noise Protocol Framework라는 framework 위에 구축되었습니다.
Noise를 이해하려면 먼저 static key와 ephemeral key의 구분을 알아야 합니다.
Static key는 WireGuard 설치 시 한 번 생성하여 설정 파일에 저장하고, peer에게 public key를 미리 배포하는 장기 key pair입니다. 곧 해당 peer의 identity입니다.
반면 ephemeral key는 handshake할 때마다 새로 생성하고 세션이 끝나면 즉시 삭제하는 일회용 key pair로, Forward Secrecy의 기반이 됩니다.
WireGuard는 Noise의 IK 패턴을 사용합니다.
- K (Known): Responder의 static public key는 이미 알고 있습니다. WireGuard는 peer 설정 시 상대방의 public key를 미리 등록하므로(`wg set wg0 peer ABCDEF...`), 첫 메시지부터 Responder의 public key로 DH를 수행하여 encrypted channel을 열 수 있습니다.
- I (Immediate): Initiator의 static public key를 첫 메시지에서 즉시 전송합니다. Responder의 public key로 AEAD encrypt하여 포함시키므로, eavesdropper는 Initiator가 누구인지 알 수 없고, Responder의 private key가 있어야만 decrypt할 수 있습니다.
이 IK 패턴이 실제 handshake 메시지에서 어떻게 나타나는지 살펴보겠습니다.
Handshake

좌측은 일반적인 handshake 상황, 우측은 responder가 load가 커서 cookie reply를 보내는 상황입니다.

- mac1, mac2: DoS 완화에서 설명한 MAC 필드입니다. initiator가 유효한 mac을 mac1 필드에 보낼 경우 responder가 cookie로 응답합니다.
- timestamp: replay attack 방지에서 설명한 필드입니다.
- Ii: 랜덤하게 생성된 값입니다. 이 메시지로 시작된 세션은 해당 값을 사용합니다.
나머지 필드들은 아래와 같이 생성됩니다.

Initiator는 랜덤한 값 Ii와 함께 ephemeral public key, 암호화된 static public key, 암호화된 타임스탬프를 보냅니다. Responder는 위 값들로 메시지의 나머지 필드들을 검증합니다.

responder는 랜덤 값 Ir을 생성한 후 마찬가지로 ephemeral public key와 암호화된 페이로드를 보냅니다.
그림에서 empty (0̂ bytes)는 0 + 16 bytes입니다. 16 bytes는 Poly1305 authentication tag입니다. AEAD에서 생성한 authentication tag를 보내는 것으로 initiator가 올바른 session key(Transport Data Keys)들을 계산했음을 증명합니다. Initiator는 이제 session keys를 symmetric key로 사용할 수 있습니다.
구체적인 필드 값 계산 식은 생략하겠습니다(논문에 나와 있습니다).

handshake가 끝나면 transport keys를 구할 수 있습니다.이제 initiator와 responder는 encapsulated 패킷을 암호화하여 보냅니다(그림의 packet).
각 transport 패킷은 counter를 가지는데, 이는 replay attack을 막기 위해서입니다.
마무리
사실 Wireguard를 처음 알아볼 때는 글로 소개할 정도로 깊이가 있을 거라고 생각하지 못했습니다. 하지만 제 예상과 달리 WireGuard의 설계 철학은 알면 알수록 인상깊었습니다.
가장 눈에 띄는 것은 스캐너에 무응답하는 silence 특성입니다. 이를 위해 1-RTT 내에 인증을 완료해야 했고, 그 요구가 Noise IK 패턴, DH, AEAD 같은 기술 선택으로 자연스럽게 이어집니다.
내부 네트워크에서 사용하는 source IP와 public 인터넷에 노출된 Internet endpoint를 분리한 CryptoKey Routing도 인상적입니다. 이 분리 덕분에 WiFi에서 모바일 네트워크로 전환해도 연결이 끊기지 않는 roaming이 별도의 메커니즘 없이 자연스럽게 동작합니다.
기존 VPN들이 복잡해진 것은 기능을 추가하면서 레이어가 쌓였기 때문인데, WireGuard는 반대로 현대적인 암호화 primitive만 고정하고 나머지를 걷어냈습니다(코드베이스가 약 4000줄에 불과합니다). 저처럼 홈서버를 외부에 노출해야 하는 경우, silence 특성과 간결한 설정은 큰 도움이 됩니다.
논문에서는 key rotation 자동화, 리눅스 커널 구현, 성능 벤치마크 등도 다루고 있지만, 이 글에서는 분량상 생략했습니다. 관심이 있다면 논문 원문을 읽어보시길 권합니다.