交易机器人的私钥防御:七件必须做到的事
部署上线时,我停下来问自己:如果这台机器被人拿了 Root,私钥还在不在?
不是假设,是必须回答的问题。账户里跑着真实资产,机器人每隔几秒签名一笔交易,私钥必须在内存里。只要在内存里,就有被读走的可能——/proc/[pid]/mem、ptrace、内存 dump,每一条都是真实的攻击路径。
我花了一天时间,把这件事认真想了一遍。想出七件事,每件都在缩小攻击面。
不过 Root 入侵不是唯一的威胁,甚至不是最常见的。实际中更高频的是开发侧的低级错误——私钥误提交 Git、CI/CD 配置泄露、日志把环境变量打出来了。其次是应用层漏洞,比如依赖链里混进恶意包。再往后才是磁盘取证、Root 入侵、云账户被盗。
下面七条,每条针对的威胁不同。
1. 私钥离线生成,传输的永远是密文
私钥在断网设备上生成,立刻用非对称加密加密成密文。从这一刻起,离开本地的只有密文。原始私钥永远不过网。
断网设备 → 生成私钥 → 非对称加密 → 密文 → 安全渠道传输
前提是生成环境本身干净。如果断网设备已经被植入了恶意软件,离线也白搭。加密用的公钥如果被替换了,密文就是给攻击者加密的。
2. 密文不落盘,只在内存里
加密后的私钥不写磁盘、不进配置文件、不进 .env。程序启动后暴露一个需要认证的接口,由我手动把密文推进去。密文只存在于进程内存。
服务器重启,内存清空,什么都不剩。攻击者拿到磁盘镜像,翻遍文件系统,找不到密钥。
但内存里的东西也可能间接落盘。VM 休眠会把 RAM 写到磁盘,swap 会把内存页交换出去,进程崩溃时的 core dump 也会写文件。这三个口子必须全堵上:关闭休眠,禁用 swap(swapoff -a),禁用 core dump(systemd 服务设 LimitCORE=0,交互式启动用 ulimit -c 0)。
Root 用户可以通过 /proc/[pid]/mem 直接读进程内存。不落盘防的是磁盘取证,不是实时入侵。注入接口必须有认证,否则谁都能往里塞东西。
3. 解密密钥拆成两半
解密密钥不完整存放在任何一个地方。一半通过环境变量注入,另一半写死在代码里,用的时候在内存中拼。
CI/CD 被翻了,攻击者只拿到 ENV 里的一半。Git 仓库泄露了,攻击者只拿到代码里的一半。两半不凑齐,解不了密。
Root 攻击者不受这个限制。即使用 deno compile 编译成二进制,strings 命令一搜、Ghidra 一反编译,写死的那一半就出来了。但多花他十分钟也是有意义的——配合第 7 条的监控,他多花的时间就是我发现和轮换密钥的窗口。
4. 明文私钥,阅后即焚
解密只发生在签名的瞬间。签完,立刻把内存覆写为零。
JavaScript 的 string 你没办法主动清除,GC 什么时候收是它的事。所以必须用 Uint8Array:
// 伪代码,展示擦除思路
// 实际签名 API 因库而异(@noble/ed25519、tweetnacl 等)
finally 不是可选项。签名抛异常也得擦,没有商量余地。decryptionKey 用完也要擦——不然解密密钥还在调用方的内存里。
V8 的 JIT 可能复制 buffer,GC 搬对象时可能留残影,编译器甚至可能把 fill(0) 优化掉。这是尽力而为。做了能缩短暴露窗口,但别指望万无一失。
5. Deno 沙箱:只给必要的权限
Deno 原生支持权限控制:
白名单之外的网络、文件、环境变量,程序碰不到。依赖链里混进了恶意包,它能干的事也被限死了。
但沙箱防的是应用层,不防 Root。Root 能 ptrace 进程、读 /proc、甚至把 Deno 二进制本身替换掉。进程级隔离,不是系统级隔离。
6. 云防火墙:管住入站和出站
在 VM 外面,通过云厂商的安全组控制网络:
- 入站:只开必要端口,来源 IP 限制到运维人员
- 出站:只许连交易所 API 和必要的基础设施
安全组在虚拟化层,VM 操作系统之外。Root 改 iptables 没用,规则不在这台机器上。木马拿到了私钥也发不出去——出站白名单里没有攻击者的服务器。
但出站白名单里不要放 Telegram API 或 SMTP。安全组按 IP/端口过滤,不管是谁在用。白名单里有 api.telegram.org,木马就能把私钥发到攻击者自己的频道——目标频道是请求参数,调用方随意指定。SMTP 同理,木马可以指定任意收件人。
告警通知用自建 webhook。通知发到哪里由 webhook 服务端决定,不由调用方控制。安全组出站只放 webhook 的 IP。
这条防不住云账户级别的攻击。攻击者拿到控制台权限,直接改安全组规则就行了。所以云账户的 MFA 和最小权限 IAM 是前提。另外 DNS 隧道可以把数据编码在查询里外发,交易所 API 的请求里也可以夹带数据,这些隐蔽通道安全组管不了。
7. 监控异常登录,随时准备轮换密钥
前面六条都在 "防"。但防御总有失效的时候。关键是:出事了,我多久能知道?
SSH 先加固。禁用密码登录,只允许密钥认证,限制可登录用户。
然后监控所有登录行为——非白名单 IP 的登录、非正常时段的 root 登录、短时间内反复失败的尝试。fail2ban 在本地就能自动封禁暴力破解 IP,不需要出站网络。
告警用自建 webhook 或者云平台监控。CloudTrail、ActionTrail 在基础设施层记录,不经过 VM 网络,也不需要额外开端口。别在 VM 上直连 Telegram 或 SMTP,原因见上一条。
一旦确认有人进来了,马上轮换密钥。这件事不能临时凑合,我提前准备了预案:在离线设备生成新私钥,加密后重新注入,同时在链上把资产转到新地址。这要花 gas、要确认转移完成、要更新所有引用旧地址的配置。我平时演练过这个流程,确保发生时能在几分钟内完成。
攻击者就算在入侵期间读到了私钥,拿到的也是一把已经作废的钥匙。
日志必须实时外发到独立的日志服务。Root 可以清本机的日志。如果日志只存本机,攻击者来过你可能根本不知道。
上线前过一遍
每次部署,问自己:
- Git 历史和文件系统里有没有未加密的密钥?
/proc/[pid]/environ能不能读到完整的解密密钥?- 明文私钥在内存里存活多久?
- 程序能不能访问白名单之外的地址?
- 安全组有没有多余的端口?
- SSH 密码登录关了没?异常登录能几分钟内通知到我吗?
- swap、休眠、core dump 关了没?
- 密钥轮换预案测试过了没?
有一个答案是 "不确定",就是还有活要干。