xavior,带 XOR 的 TCP 端口转发程序

受 UDPspeeder 启发,我一直想有一个 TCP 的带 XOR(异或)加密的端口转发程序。某些情况下我不希望流量完全明文传输,但也不至于用到 AES 甚至 TLS 那样强加密通道的程度。只需要简单的混淆,那 XOR 非常好使。不过似乎没有现成程序,我得自己编写一个。最近终于又出现实际用例,搜了 GitHub 没发现符合需求的,索性熬夜写了一个,命名为 xavior

XOR 是一种基本位运算。两个相同的位(二进制数)结果为 0,不同为 1。它是一些现代加密算法的基本逻辑单元,也可独立视作加密算法。并且,明文按位异或密钥得到密文,再按位异或即得明文。所以不仅加密、解密密钥相同,函数也是完全一样的。作为加密算法,XOR 非常快,也很不安全。理论上,只要密钥长度大于消息长度,且密钥每次随机产生,XOR 可以获得完美保密性。但这种一次性密码本不太现实。所以 XOR 不被视作正经安全的加密算法。但如果我们只是稍微象征性地加密、混淆流量,那 XOR 实现简单、效率高,还算是可取。

设计思路

基本 XOR 加密实现非常简单,伪代码如下:

byte[] message = "message";
byte[] key = "ThisIsKey";
for (i = 0; i < len(message); i++) {
    message[i] ^= key[i % len(key)];
}

关键在于 ^= 运算符,原地按位异或。有需要也可定义专门数组储存密文。没有什么复杂的操作。这里循环使用了 key,也就是相当于当密钥 key 不如消息 message 长时,将 key 的内容重复拼接以将按位异或进行下去。在实际使用中,key 可能会是随机生成的二进制内容,像上例那样采用文本相对来说更不安全;因为显然 ASCII 码高位是 0,异或之后的结果相当可预测。

我设计 xavior,仍然接收用户传入的可读文本作为 password,然后进行 SHA-512 转换成为异或加密的密钥 key。这样既方便人类读写,又兼顾安全性——当然是在 XOR 框架下的安全性,请注意它作为加密算法本身就不安全。

这样加密后的流量,表面看起来比较随机。XOR 加密不会引入填充(padding),可以看作流加密。也不引入其他初始向量(IV)之类的额外数据。所谓 password 是用来生成的 key 的,不引入额外的 RTT 作认证。总体来看传输流量是按字节变换的,没有引入多余的东西。

我选用 Go 语言实现这个程序。一方面我想学习 Go 语言,另一方面 Go 语言也很方便编译跨平台程序,有简单易用的网络 IO、多线程库。实际上,我只要问问 GPT 大体的代码,在网上搜索 Go 语言端口转发示例,组合起来就行了。

由于 XOR 的特性,同一个函数既能加密又能解密,所以不需要在程序上区分服务端、客户端。只需要编写、编译一个程序,也无需参数显式区分。

这里我设计两个密码,将发送/上行密码、接收/下行密码区分开。这是考虑到 XOR 加密比较弱,相对容易从密文猜测出原文从而得到密钥,尤其对一些应用层协议来说响应(特别是回报错误的响应)比较固定。那么将两个密码(密钥)区分开有助于在主动探测场景下增加安全性。窃听者我就不管了。有严肃安全性需求的应该寻求更专业的工具。这个流程大概如下:

{ [service(server -r)] <-- [<--sp-- xavior(server -l / client -r)] } <--- { [<--sp-- xavior(client -l)] <-- [client] }
{ [service(server -r)] --> [xavior(server -l / client -r) --rp-->] } ---> { [xavior(client -l) --rp-->] --> [client] }

相关源码可以在我的 GitHub 仓库看到,本文不再完整贴上。

实现踩坑

这里的坑是关于 read buffer 的。前述伪代码给出的循环使用 key,结合网络 IO 时容易出错。TCP 的读写有缓冲区(buffer),通常设为 4k ~ 32k 左右,一次最多读出这么多字节的数据。流量转发也是转发这么多。但是,千万不要以为设置一个 4096 的 buffer 数组,就一定能读出 4096 字节。这里没有保证,我是说即使排除最后一轮临近 EOF 的读入,实际上在 Linux 下就是可能不会读满。那么下列写法就是错误的:

    buf := make([]byte, BUFFER)
    for {
        nr, er := src.Read(buf)
        if nr > 0 {
            goodBuf := buf[:nr]
            for i := 0; i < len(goodBuf); i++ {
                goodBuf[i] ^= key[i%len(key)]
            }
            nw, ew := dst.Write(goodBuf)
    }

这里 key 并没有被循环利用,而是可能用到中间,又从头开始使用了。因为 buf 并不一定会读到 BUFFER 那么长的数据。这样会导致解密错误而乱码。当然也不能为此就说改用 ReadFull 一次性全部读出,这样性能堪忧。解决方法是加一个指向 key 数组的索引变量,严格要求其从头到尾走完 key 再回到开始就行:

    buf := make([]byte, BUFFER)
    XORKeyIndex := 0
    for {
        nr, er := src.Read(buf)
        if nr > 0 {
            goodBuf := buf[:nr]
            for i := 0; i < len(goodBuf); i++ {
                // don't just do `goodBuf[i] ^= key[i%len(key)]` cause `nr < len(buf)` happens
                goodBuf[i] ^= key[XORKeyIndex]
                XORKeyIndex = (XORKeyIndex + 1) % len(key)
            }
            nw, ew := dst.Write(goodBuf)
    }

编译

像通常编译 Go 程序那样编译 xavior 就行了:

go build -ldflags "-s -w" xavior.go

得益于 Go 强大的组建工具,可以很方便交叉编译。如在 Windows 上编译 Linux-amd64 可执行程序:

SET CGO_ENABLED=0
SET GOOS=linux
SET GOARCH=amd64
go build -ldflags "-s -w" main.go

在 Linux 上编译 Windows-amd64 程序:

CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build "-s -w" main.go

测试

程序写好就可以测试了。我本来想用 telnet、nc 之类的工具,后来发现不太好。nc 即 ncat 可以测简单短小的 TCP 报文收发,但组装长汉字文章似乎有 bug。后来我干脆直接上 iperf3,验证正确传输的同时可以测试性能。

在内网 Linux 服务器上运行服务端:

iperf3 -s -p 6000
./xavior -l "0.0.0.0:6666" -r "127.0.0.1:6000" -sp "123456" -rp "654321"

在个人 Windows 电脑上运行 xavior 作为客户端,然后执行 iperf3 连接(我这里用 WSL 的 iperf3):

xavior -l "127.0.0.1:6999" -r "192.168.1.2:6666" -sp "123456" -rp "654321"
iperf3 -c 127.0.0.1 -p6999 -R -n 4194304000

注意发送密码(-sp)、接收密码(-rp)的顺序不需要调换。

测试能够跑通,且跑满千兆局域网,没有瓶颈。

实际使用一段时间,暂时没有发现严重问题。

适用场景

回到文章最开头提到的我的场景。实际上我是想将 RDP 之类的端口开放在公网使用,不好设置 IP 白名单,不想隐藏在 VPN 下面,也不想为用 SSH 端口转发而新建用户设置权限。我觉得 RDP 一般来说足够安全,但确实需要预防脚本小子,所以像 XOR 这样的简单加密很符合需求,隐藏端口协议,兼有认证的作用。

也许还有别的使用场景。注意 XOR 加密总归不安全,请全面考虑。

有需要可以下载我的预编译文件:https://github.com/shansing/xavior/releases

若无特别说明,本文系原创,遵循 署名-非商业性使用 3.0 (CC BY-NC 3.0) 协议,转载文章请注明来自【闪星空间】,或链接上原文地址:http://shansing.com/read/538/

2 条评论

  1. 不明觉历,但还是来评论一发。

  2. 诶嘿诶嘿呦 诶嘿诶嘿呦

    开个打赏,我打赏五块表示诚意。 因为这个功能我之前也想写,也是想用于远程桌面和ssh。
    最近服务器的web服务也需要这个工具,花了好长时间找软件,万万没想到最后遇到你。
    想法一样,我只需要一个能简单加密端口的工具

发表评论»

NO SPAMS! 不要发垃圾评论哦!

表情