Tailscale学习笔记
Tailscale学习笔记
学习时间:2025年5月9日
1 前置知识
1.1 密码学简介
1.1.1 密钥
在密码学中,加密的本质是将明文 $P$ 通过加密函数 $E$ 和密钥 $K$ 生成密文 $C$:
$$
C=E_K(P)
$$
解密过程就是使用解密函数 $D$ 和同一个密钥 $K$(如果是对称加密):
$$
P=D_K(C)
$$
这个密钥 $K$ 是整个系统的安全核心,任何没有密钥的人都无法从 $C$ 还原出 $P$。
1.1.2 加密算法
加密算法分 对称加密 和 非对称加密,其中对称加密算法的加密与解密 密钥相同,非对称加密算法的加密密钥与解密 密钥不同,此外,还有一类 不需要密钥 的 散列算法。
- 对称加密:对称加密算法 是应用较早的加密算法,又称为 共享密钥加密算法。在 对称加密算法 中,使用的密钥只有一个,发送 和 接收 双方都使用这个密钥对数据进行 加密 和 解密。这就要求加密和解密方事先都必须知道加密的密钥。
数据加密过程:在对称加密算法中,数据发送方将明文 (原始数据) 和加密密钥一起经过特殊加密处理,生成复杂的加密密文进行发送。
数据解密过程:数据接收方收到密文后,若想读取原数据,则需要使用加密使用的密钥及相同算法的逆算法对加密的密文进行解密,才能使其恢复成可读明文。
- 非对称加密:非对称加密算法,又称为 公开密钥加密算法。它需要两个密钥,一个称为 公开密钥 (
public key
),即 公钥,另一个称为 私有密钥 (private key
),即 私钥。
1.1.3 对称加密
主流的算法如下表所示:
算法名称 | 类型 | 简要说明 |
---|---|---|
AES | 分组加密 | 当前最主流的标准加密算法,安全、高效,固定128位块 |
DES | 分组加密 | 旧标准,56位密钥,已不安全 |
3DES | 分组加密 | 对DES进行三重加密,安全性提高,但效率低 |
RC4 | 流加密 | 速度快,但已被废弃,容易被破解 |
ChaCha20 | 流加密 | 更现代、更安全的流加密算法,速度快,用于TLS/HTTPS |
Blowfish | 分组加密 | 可变密钥长度,结构简单,适合嵌入式 |
TEA/XTEA | 分组加密 | 非常轻量的加密算法,适合资源受限环境 |
① AES(Advanced Encryption Standard)
AES 是一种分组对称加密算法,即每次加密固定大小的一段数据(通常为128位 = 16字节),加密和解密使用相同密钥。
- 明文 $P$:128 位数据,表示为 $4 \times 4$ 字节矩阵(State)
- 密钥 $K$:128/192/256 位,分成若干轮密钥(Round Key)
明文矩阵例如(假设为 16 字节):
$$
\text{State} = \begin{bmatrix}
p_0 & p_4 & p_8 & p_{12} \
p_1 & p_5 & p_9 & p_{13} \
p_2 & p_6 & p_{10} & p_{14} \
p_3 & p_7 & p_{11} & p_{15}
\end{bmatrix}
$$
数学推导暂略
1.1.4 非对称加密
算法名称 | 基于数学原理 | 应用领域 |
---|---|---|
RSA | 大整数分解(模幂运算) | SSL/TLS、电子签名、加密通信 |
ECC | 椭圆曲线离散对数问题 | 移动设备、SSL、区块链、签名 |
ElGamal | 离散对数问题 | 数字签名、加密通信 |
DSA | 离散对数问题(用于签名) | 数字签名标准 |
EdDSA | 基于椭圆曲线优化签名 | 高性能签名,如 Ed25519 |
PQ加密 | 量子抗性算法(NTRU, Kyber) | 未来抗量子攻击密码(研究中) |
1.1.5 公钥和私钥
公钥(Public Key)和私钥(Private Key)是非对称加密(asymmetric encryption)中的一对密钥,它们是现代加密技术、VPN、HTTPS、数字签名等系统的核心。
私钥(Private Key):保密的,只能自己持有。
公钥(Public Key):可以公开给任何人。
它们总是成对出现,并满足以下两个核心特性:
用公钥加密的内容,必须用私钥解密(公钥加密,私钥解密)
用私钥签名的内容,可以用公钥验证(私钥签名,公钥验证)
应用场景
应用场景 | 公钥作用 | 私钥作用 |
---|---|---|
数据加密 | 加密数据 | 解密数据 |
数字签名 | 验证签名是否合法 | 生成签名 |
身份认证 | 验证身份 | 证明身份 |
VPN/WireGuard | 标识对方设备身份 | 加密解密流量/握手密钥 |
与对称加密的区别
加密方式 | 密钥数量 | 是否安全公开 | 应用场景 |
---|---|---|---|
对称加密 | 1 个密钥 | ❌ 必须保密 | AES、文件本地加密等 |
非对称加密 | 一对密钥 | ✅ 公钥可公开 | HTTPS、VPN、身份验证、GPG 等 |
1.2 SSL/TLS
1.2.1 简介
SSL(Secure Sockets Layer, 安全套接层) 和 TLS(Transport Layer Security, 传输层安全协议) 都是为网络通信提供安全保障的加密协议。
TLS是 SSL 的升级版,修复了早期 SSL 版本(如 SSLv2、SSLv3)的安全漏洞。目前广泛使用的是 TLS 1.2 和 TLS 1.3,但习惯上仍统称为SSL。现在浏览器所说的“HTTPS 安全”就是建立在 TLS 协议之上的。
1.2.2 证书
SSL/TLS 证书是由权威机构(CA)签发的数字文件,用于证明网站身份,并提供服务器公钥。
证书内容包括:
- 域名(如
example.com
) - 证书持有者信息(公司、组织等)
- 公钥(用于加密密钥)
- 有效期(开始和结束时间)
- 颁发者(CA 的名称,如 DigiCert, Sectigo, Let’s Encrypt)
- 签名(由 CA 使用私钥对证书进行的签名)
证书可以分为几种类型:
类型 | 说明 |
---|---|
DV 证书 | Domain Validation,仅验证域名所有权,快速颁发 |
OV 证书 | Organization Validation,验证公司身份 |
EV 证书 | Extended Validation,最严格,浏览器地址栏显示绿色 |
自签名证书 | 自己生成的,不可信(浏览器会提示不安全) |
1.2.3 握手流程
HTTPS 的安全性主要体现在 TLS 握手过程中使用了 混合加密机制: 非对称加密(RSA/ECDSA) + 对称加密(AES等) + 哈希算法(SHA)。
握手阶段涉及4次通信,并且在该阶段中传送的数据都是明文的。
- 客户端发起请求(
ClientHello
)
客户端(通常是浏览器)先向服务器发出加密通信的请求,客户端主要向服务器提供以下信息。
- 支持的协议版本,比如TLS 1.0版
- 一个客户端生成的随机数(
ClientRandom
),稍后用于生成对话密钥 - 支持的加密方法,比如AES加密
- 支持的压缩方法
- 服务器回应(
ServerHello
)
服务器收到客户端请求后,向客户端发出回应,服务器的回应包含以下内容:
- 确认使用的加密通信协议版本,比如TLS 1.0版本。如果浏览器与服务器支持的版本不一致,服务器关闭加密通信
- 一个服务器生成的随机数(
ServerRandom
),稍后用于生成对话密钥 - 确认使用的加密方法,比如AES加密
可以看到,服务端选择的密码套件是Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256
。
- 服务器证书,证书内容详见上一节
- 索要客户端证书(非必须):除了上面这些信息,如果服务器需要确认客户端的身份,就会再包含一项请求,要求客户端提供”客户端证书”。比如,金融机构往往只允许认证客户连入自己的网络,就会向正式客户提供USB密钥,里面就包含了一张客户端证书。
- 客户端回应(ClientCertificate)
客户端收到服务器回应以后,首先验证服务器证书。如果证书不是可信机构颁布、或者证书中的域名与实际域名不一致、或者证书已经过期,就会向访问者显示一个警告,由其选择是否还要继续通信。
如果证书没有问题,客户端就会从证书中取出服务器的公钥,然后,向服务器发送下面信息:
- 一个随机数(被称为
Pre-Master Secret
, 预主密钥),该随机数用服务器公钥加密,防止被窃听
- 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送
- 客户端握手结束通知,表示客户端的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供服务器校验
- 客户端利用生成的三个随机数,基于之前约定的加密方法(RSA),生成会话密钥(
Master Secret
)。客户端和服务器使用该对称密钥进行加解密。
生成完「会话密钥」后,然后客户端发一个「Change Cipher Spec」,告诉服务端开始使用加密方式发送消息。
然后,客户端再发一个「Encrypted Handshake Message(Finishd)」消息,把之前所有发送的数据做个摘要,再用会话密钥(master secret)加密一下,让服务器做个验证,验证加密通信「是否可用」和「之前握手信息是否有被中途篡改过」。
可以发现,「Change Cipher Spec」之前传输的 TLS 握手数据都是明文,之后都是对称密钥加密的密文。
- 服务器回应(ServerCertificate)
服务器收到客户端的第三个随机数pre-master key
并解密之后,同样基于之前约定的加密方法(RSA),使用三个随机数计算生成本次会话所用的会话密钥。然后,向客户端最后发送下面信息。
- 编码改变通知,表示随后的信息都将用双方商定的加密方法和密钥发送。
- 服务器握手结束通知,表示服务器的握手阶段已经结束。这一项同时也是前面发送的所有内容的hash值,用来供客户端校验。
- 正式进行加密通信
握手完成后,后续的网页内容、表单数据等都通过会话密钥(对称密钥)加密传输。
2 核心目标
构建一个零配置的、端到端加密的私有网络,使得即使两台设备在 NAT/防火墙/公网中,也能像同处局域网一样互通。
Tailscale 使用 WireGuard 协议作为底层数据加密手段。每台设备都有一个静态的公私钥对:
- 私钥仅保存在本地设备
- 公钥上传到控制服务器用于身份识别
整体通信流程:
- 设备启动时自动生成 WireGuard 密钥对
- 登录时使用 OAuth(Google、GitHub 等)认证绑定用户身份
- 控制服务器下发受信任节点列表给设备
- 两个设备直接建立 WireGuard 隧道,详见4.3节密钥协商
3 架构
Tailscale 架构可分为三层:
层级 | 功能 |
---|---|
控制层(control plane) | 提供身份认证、密钥管理、节点列表分发(由 Tailscale 提供协调服务器) |
数据层(data plane) | 真正的数据通信通道,点对点传输,使用 WireGuard 加密 |
中继层(relay plane) | 若点对点失败,使用 DERP 中继服务器转发 |
4 技术细节
4.1 协调服务器
控制层中,Tailscale提供一个集中式协调服务器(coordination server, login.tailscale.com
)用于存储每一个加入到同一个tailnet
的客户端的公钥,用于身份识别认证,如下图:
与协调服务器的联系流程:
- 每个节点为自己生成一个随机的公钥/私钥对,并将公钥与其身份相关联
- 该节点联系协调服务器并留下其公钥以及有关该节点当前位置以及所在域的注释
- 该节点下载其域中的公钥和地址列表,这些列表已被其他节点留在协调服务器上
- 节点使用适当的公钥集配置其 WireGuard 实例
4.2 双因素身份验证(2FA、MFA)
Tailscale 不处理用户身份验证,而将身份验证外包给OAuth2、OIDC(OpenID Connect)或 SAML 提供商。常用的提供商包括 Gmail、GSuite 和 Office365。
这样,协调服务器只将公钥下载到加入到经过某个身份验证后(例如Google账户)的每个节点(尽管将公钥公布到公网上也是安全的),每个节点都为 WireGuard 配置一个超轻量级隧道,连接到其他节点。
4.3 密钥协商
WireGuard 使用 Curve25519 进行 密钥交换,用 ChaCha20-Poly1305 进行 加密 + 完整性验证,构建出一个 轻量、高速、安全的加密隧道。
A和B的通信流程图:
1 | sequenceDiagram |
4.3.1 密钥生成
每台设备初始化时会生成一对密钥:
- 私钥
sk
是一个随机 32 字节数(保存在本地) - 公钥
pk = sk · G
,G
是 Curve25519 的生成元
A和B向协调服务器获取其他节点的公钥,于是:
1 | A拥有:sk_A, pk_A,pk_B |
4.3.2 握手过程
A 向 B 发起握手请求:
- 生成 Ephemeral 公钥
epk_A = esk_A · G
(临时密钥对) - 附带时间戳、防重放 nonce、MAC 校验等
- 生成 Ephemeral 公钥
双方用 Curve25519 执行 Diffie–Hellman 运算,各自生成共享会话密钥:
$$
\text{K_s} = \text{ECDH}(sk_A, pk_B) = \text{ECDH}(sk_B, pk_A)
$$结合其他参数(时间戳、会话 ID 等),使用 HKDF 派生:
- 加密密钥
K_enc
- MAC 密钥
K_mac
- 加密密钥
特性:
- 密钥每 2 分钟左右重新握手更新
- 临时密钥(ephemeral)用于前向保密(PFS)
4.3.3 加密通信
此时通信成为对称加密过程。
加密:使用:ChaCha20对称加密算法(快 + 不需要硬件加速)和 Poly1305消息认证码(MAC)防篡改。
发送加密数据:
ciphertext = ChaCha20_Encrypt(K_enc, nonce, plaintext)
tag = Poly1305(ciphertext || header, K_mac)
最终数据包结构如下:
1 | | Header (Src IP, nonce) | Ciphertext | MAC (Poly1305 tag) | |
解密:
- B 使用密钥
K_enc
,K_mac
- 验证 MAC(Poly1305)
- 使用
K_enc
解密数据
若 MAC 不通过,则丢包(防止篡改和伪造)
5 NAT穿透
暂略
6 DERP
6.1 简介
在 Tailscale 中,DERP(Designated Encrypted Relay for Packets,指定加密中继服务) 是一个关键的组件,用于解决 NAT 穿透失败时的中继通信问题。
正常情况下,节点之间可以通过 NAT 穿透(如 UDP Hole Punching)进行直连通信。但在某些情况下(如企业网络、NAT 类型严格、UDP 被阻断),节点之间无法建立直接连接,这时就需要一个中继服务器,这就是 DERP。
6.2 工作方式
DERP 服务器是 纯中继服务器,只负责转发经过端到端加密的流量。它:
- 使用 TCP + TLS(HTTPS) 与节点通信,确保链路加密;
- 不会解密数据内容,因为数据已经被 WireGuard 加密,确保了端到端加密;
- 每个节点默认连接一个最近(或最优)的 DERP 服务器;
- 仅在点对点失败时才启用 DERP。
6.3 抓包分析
使用一个自定义的DERP服务器,进行抓包实验:
图中被打码的IP地址为DERP服务器的地址。
6.3.1 TLSv1.2 Application Data
例如430-433等,这些都是:
1 | Protocol: TLSv1.2 |
表示此时通信两端之间在传输加密的应用层数据。由于 TLS 是加密的,这些内容不可见,但它包裹的实际是 WireGuard 加密后的数据包(Tailscale 会在 TLS 上层传输 DERP 协议,再传 WireGuard 封装数据)。
6.3.2 STUN Binding Request / Binding Success Response
例如544,555行:
1 | Protocol: STUN |
其中Binding Request
由内网节点发往DERP,Binding Success Response
由DERP发往内网节点。
这是一对UDP包:
DERP向内网节点发送的响应中,包含了内网节点在穿越NAT后的公网地址和端口:
XOR-MAPPED-ADDRESS
: 经过XOR(异或)加密后的IP地址和端口,经过客户端解密后会得到例如:xxx.xxx.xxx.xxx:25888
- 报文中的详细信息:
Port(XOR-d)
:经过异或后的端口IP(XOR-d)
:经过异或后的IP地址
作用:这两个数据包是用来进行 STUN 协议的 NAT 穿透尝试的——优先尝试 UDP 直连(P2P),失败才走 DERP。
6.3.3 TCP ACK 报文
例如:
1 | TCP 66 xxxxx → 53944 [ACK] Seq=... Ack=... Len=0 |
这些是纯粹的 TCP 确认包,用来维持连接的可靠传输,不携带实际数据。
6.3.4 其他注意点
打洞(STUN)操作发生在通过 DERP 中继传输数据之后
原因是:Tailscale 设计中,DERP 会先用作临时“备胎”中继通道,然后后台再继续尝试 P2P 打洞穿透,目标是让两端尽快“能通信”,哪怕一开始不是最优路径。
抓包时发现不时有STUN请求和响应
Tailscale 设计的核心目标之一是 在网络环境变化时,始终争取使用最优路径。因此,即使已经建立连接,也会:
- 动态监控网络变化
- 维持 NAT 映射(keep-alive)
UDP NAT 绑定如果一段时间没有流量,可能会被路由器/防火墙清除,导致直连路径断开。
为了避免这种情况,Tailscale 会:
- 每隔几十秒(典型值约为 30 秒)发送 STUN Binding Request;
- 这不仅探测路径,也顺便刷新 NAT 映射,保持打洞通道活跃。
- 尝试中继回直连
即使当前连接走的是 DERP(中继服务器),Tailscale 仍然周期性尝试重新打洞以回到更快的 UDP 直连路径,尤其在:
- 双方 NAT 类型较难穿透(如双对称 NAT);
- 某一端公网 IP 动态变更后路径可达性恢复。
6.4 搭建自定义DERP
6.4.1 准备工作
- 购买云服务器、域名
- 域名解析
- SSL证书申请,将证书和私钥上传至服务器
- 假设服务器IP为
10.10.10.10
,域名为derp.example.com
,证书和私钥存放目录为/usr/local/cert
6.4.2 安装Tailscale客户端
目的:让derp服务器加入属于自己账号的tailnet
。
- 下载amd64位deb包,版本可替换:
1 | https://dl.tailscale.com/stable/tailscale_1.72.1_amd64.deb |
其他操作系统和架构,参考:https://pkgs.tailscale.com/stable/
- 安装和启动
1 | dpkg -i tailscale_1.82.5_amd64.deb |
启动后控制台出现链接,点击链接跳转登陆即可。
6.4.3 配置Golang环境
略
6.4.4 安装和启动DERPER服务
创建目录并进入:/usr/local/derper
下载:
1 | GOPROXY=https://goproxy.cn,direct go install tailscale.com/cmd/derper@latest go install tailscale.com/cmd/derper@latest |
下载完成后,目录中出现derper
可执行文件。
执行:
1 | nohup ./derper |
命令解释:
部分 | 含义 |
---|---|
./derper |
执行当前目录下的 DERP 服务器可执行文件 |
-hostname derp.example.com |
指定该 DERP 节点的主机名(用于 TLS 证书和客户端识别) |
-c=derper.conf |
加载 derper.conf 配置文件 |
-a :12345 |
指定监听地址和端口(如 :443 表示监听所有 IP 上的 443 端口) 12345 应替换为实际端口号 |
-http-port -1 |
禁用额外的 HTTP 监听端口(仅使用 -a 中的端口) |
-certdir /usr/local/cert |
指定证书存放目录(证书文件应该命名为 ${hostname}.crt ,私钥为 ${hostname}.key ) |
-certmode manual |
使用手动提供的 TLS 证书而非自动获取(例如不使用 Let’s Encrypt) |
-verify-clients |
启用客户端验证(如仅允许使用有效的 Tailscale keys 的客户端连接),防止其他未认证节点盗刷DERP服务器流量 |
-stun |
启用 STUN 功能,用于帮助客户端穿透 NAT |
启动成功后,服务器需要开放相应端口12345
(HTTPS)和3478
(STUN)。
测试:输入域名:12345
6.4.5 客户端配置
进入Tailscale账户主页的Access controls
,添加以下内容:
1 | { |
测试:在客户端中输入tailscale netcheck