看到你也能写个shadowsocks这篇文章,对ss代理豁然开朗,忽然发现ss也不是一个特别高大上的项目,加上最近学习设计模式,不想写业务逻辑,就想用java实现个低配的shadowsocks。仓库:lightsocks-java
基本原理
基本原理上面提到的那篇文章《你也能写个shadowsocks》已经讲了很清楚了,主要技术就是加密,解密,socks5代理
加密
为了“强行”体现设计模式(~逃),这里用了个Strategy模式,把加密模块抽象化,每个加密模块必须实现encrypt()
和decrypt()
方法,接口Cryptor中包含了一个静态的工厂方法,根据加密字符串选择实例化加密对象返回,于是这里又体现了Factory模式(笑)。实际证明后续配合上配置文件使用,这样写的确好了不少(~设计模式真是有用啊)public interface Cryptor {
public abstract byte[] encrypt(byte[] data);
public abstract byte[] decrypt(byte[] data);
public static Cryptor createNewCryptor(String method,String key) {
method = method.toLowerCase();
switch (method) {
case "aes-256-cfb":
return new AES_256_CFB(key);
case "none":
return new NoPasswordCryptor();
case "simple":
return SimpleCrypto.getSimplieCrypto(key);
}
return null;
}
}
当前实现的加密模式只有三个,严格说一个半。。。
AES
首先第一个是aes
分块加密算法,按理来说aes应该是唯一比较安全的。可能我对密码学毫不理解,对与aes加密只停留在调包套模板上,实际后来测试下一半情况下是能用的,各种长度不为16倍数的exception……还是等我学习了密码学后再来优化吧。
none
如果空白加密也算是一个加密方法的话,这个可以算上(~逃)
simple
这是原作者文章中实现的一个简单的加密算法,大概就是说,用0-255的乱排序构造一个密钥,可以生成两个array,对每个byte(取值0-255)根据数组进行映射,解密加密前长度一样。起本质上就是一个置换密码,密钥的种类有255的乱排也就是254!个之多。但是实际上这种密码也可以根据统计规则被轻易破解的,相信进化到用机器学习识别流量特征的墙应该不会破不出来。所以这个也只能暂时做个简单加密。但是这种算法的确是最高效的,加密解密都是O(n),前后长度不变化。尽管AES很强,但是我菜啊QAQ
转发
客户端转发就是把一个socket的输出接到另一个的输入上,就是用inputStream
和outputStream
构造一个转发类,无脑的encryptForward或者decryptForward了。相比客户端,服务端无非就是处理socks5连接的建立,获取真实的remote的地址,然后继续无脑encryptForward或者decryptForward。当然为了减少多个应用同时请求带来的卡顿,开个多线程,举个Local线程中的例子:new EncryptForward(localIn,hostOut,Local.cryptor).start();
new DecryptForward(hostIn,localOut,Local.cryptor).start();
再列举一下EncryptForward类的例子,举着一个即可,解密转发线程无非就是encrypt换成decrypt。网上很少有java写代理的,这里参考了chinashiyu的gfw.press中的一部分代码,自动调整缓冲区大小,可能是为了性能考虑(然而并没有看懂他的项目里服务器端处理socks5连接过程的代码)public class EncryptForward extends Thread {
private InputStream in;
private OutputStream out;
private byte[] buffer;
private Cryptor cryptor;
private boolean isRunning;
private static final int BUFFER_SIZE_MIN = 1024 * 128; // 缓冲区最小值,128K
private static final int BUFFER_SIZE_MAX = 1024 * 512; // 缓冲区最大值,512K
private static final int BUFFER_SIZE_STEP = 1024 * 128; // 缓冲区自动调整的步长值,128K
public EncryptForward(InputStream in, OutputStream out, Cryptor cryptor) {
this.buffer = new byte[BUFFER_SIZE_MIN];
this.in = in;
this.out = out;
this.isRunning = true;
this.cryptor = cryptor;
}
public void run() {
try {
int len = 0;
while ((len = in.read(buffer)) != -1 && isRunning) {
byte[] rawData = Arrays.copyOfRange(buffer, 0, len);
byte[] encryptData = cryptor.encrypt(rawData);
if (encryptData == null) {
break; // 加密出错,退出
}
out.write(encryptData);
out.flush();
if (len == buffer.length && len < BUFFER_SIZE_MAX) { // 自动调整缓冲区大小
buffer = new byte[len + BUFFER_SIZE_STEP];
} else if (len < (buffer.length - BUFFER_SIZE_STEP) && (buffer.length - BUFFER_SIZE_STEP) >= BUFFER_SIZE_MIN) {
buffer = new byte[buffer.length - BUFFER_SIZE_STEP];
}
}
} catch (SocketException e) {
isRunning = false;
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试
调试中的测试可以用python的黑科技:socks包,直接开一个本地代理socks,让自己写的lightsocks_local跑在8080端口,lightsocks_server泡在8090,以下代码会发出一次socks5连接代理请求。如果成功输出报文,可以在浏览器中开启代理进一步测试了import socks
s = socks.socksocket()
s.set_proxy(socks.SOCKS5, "localhost", 8080)
s.connect(("baidu.com", 80))
s.sendall(str("GET / HTTP/1.1\n").encode())
print (s.recv(4096))
把server跑在vps上,开启代理,google打开,就说明成功了。
总结
这种写出来的ss也只是学会了网络代理的一点皮毛,真实的ss比这个就加密算法而言不知道高到哪里去了,还有ssr的混淆黑科技。本项目仅作技术讨论学习,请勿用于非法用途!