本文主旨
Rc4
是一种对称加密方式,且属于流加密,本文讲解如何使用Java
做Rc4
的加密解密操作,并且讲解Shadowsocks
是如何使用Rc4
加密和Rc4md5
加密的。
Rc4
加密解密例子
Rc4
需要一个密钥来加密。
下面这段代码演示了,如何使用Java
自带的Cipher
做Rc4
加密解密。密钥使用的是随机生成的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
@Test public void testRc4Origin() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException { String password = randomString(20); String content = randomString(50); Cipher encoder = Cipher.getInstance("RC4"); encoder.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(password.getBytes(), "RC4"));
Cipher decoder = Cipher.getInstance("RC4"); decoder.init(Cipher.DECRYPT_MODE, new SecretKeySpec(password.getBytes(), "RC4"));
byte[] update = encoder.update(content.getBytes()); byte[] update1 = decoder.update(update);
Assert.assertEquals(content, new String(update1, StandardCharsets.UTF_8));
String content2 = randomString(50); byte[] en = encoder.update(content2.getBytes()); byte[] de = decoder.update(en); Assert.assertEquals(content2, new String(de, StandardCharsets.UTF_8));
}
private static String randomString(int length) { StringBuilder sb = new StringBuilder(length); Random random = new Random(); for (int i = 0; i < length; i++) { int rand = random.nextInt('z' - 'a' + 1) + 'a'; sb.append((char) rand); } return sb.toString(); }
|
Shadowsocks
使用的Rc4
上面示例可以看出,Rc4
是流式加密,且初始化Cipher
时只需要传递密钥即可。Shadowsocks
里面的Rc4
算法和上面是一样的,使用上面的代码就能解析Shadowsocks
发出的Rc4
算法加密的数据。
但在新版本里面已经移除了Rc4
算法,因为用户经常为了方便使用弱密码,导致Rc4
强度不足。
Shadowsocks
改进Rc4
为Rc4Md5
算法
之所以抛弃Rc4
并不是Rc4
本身加密强度不够,而是因为用户使用弱密码,所以只要增加密码位数就能得到安全的加密。
加强密钥
既然用户会提供弱密码,那Shadowsocks
就想办法将密钥加强,它的做法是:
1
| realPassWord = md5(password)
|
因为md5
算法得到的128bit
摘要数据,用作密钥要比用户自己输入的要强一些。
避免特征
上面解决了弱密码的问题,但又有另一个问题出现了,因为密码是固定的,得到的md5
也是固定的,也就是说每次连接使用的都是相同的密钥,这样时间长了可能被发现特征。于是有了下面的办法让每个连接使用的密码都不一样。
解决办法是每个连接在数据流前先发送16B
的随机值称为IV值
,与上面得到的密钥进行混合,这样每个连接流使用的密钥就不同了。
1 2 3 4 5 6 7 8 9 10
| public static byte[] md5IvKey(byte[] originPassword, byte[] iv) throws NoSuchAlgorithmException { MessageDigest md5 = MessageDigest.getInstance("md5"); byte[] md5Password = md5.digest(originPassword); md5.reset(); md5.update(md5Password); md5.update(iv); return md5.digest(); }
|
上面代码抽象就是:
1
| realPassWord = md5(md5(password),iv);
|
由上是Shadowsocks
内部的Rc4md5
的工作原理。他是使用的Rc4
做加密解密手段,并通过md5
算法混合用户输入的密码+IV,每次使用的密钥都不相同,降低流量特征。
在我们程序中借鉴此方案
在我写的内网穿透软件中rmp,客户端和服务端通信需要实现加密传输,并且服务端和客户端配置相同的Token
来验证。就可以参考上面的方式来加密数据。
写一个Rc4Md5Handler
继承ByteToMessageCodec
来将输出数据加密,输入数据解密。`
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| package com.fys.cmd.handler;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageCodec;
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.security.SecureRandom; import java.util.List;
import static com.fys.cmd.util.CodeUtil.md5; import static java.nio.charset.StandardCharsets.UTF_8;
public class Rc4Md5Handler extends ByteToMessageCodec<ByteBuf> {
private final static SecureRandom random = new SecureRandom(); private boolean firstDecode = true; private boolean firstEncode = true; private Cipher decoderCipher; private Cipher encoderCipher;
private String password;
public Rc4Md5Handler(String password) { this.password = password; }
@Override protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception { if (firstEncode) { byte[] iv = randomIv(); encoderCipher = Cipher.getInstance("RC4"); encoderCipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(md5(md5(password.getBytes(UTF_8)), iv), "RC4")); firstEncode = false; out.writeBytes(iv); } out.writeBytes(encoderCipher.update(readByte(msg, msg.readableBytes()))); }
@Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { if (firstDecode) { byte[] iv = readByte(in, 16); decoderCipher = Cipher.getInstance("RC4"); byte[] realPassWord = md5(md5(password.getBytes(UTF_8)), iv); decoderCipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(realPassWord, "RC4")); firstDecode = false; } out.add(Unpooled.wrappedBuffer(decoderCipher.update(readByte(in, in.readableBytes())))); }
private byte[] readByte(ByteBuf in, int length) { byte[] bytes = ByteBufUtil.getBytes(in, in.readerIndex(), length); in.skipBytes(bytes.length); return bytes; }
private byte[] randomIv() { return random.generateSeed(16); }
}
|
将删改你的Handler
添加到server
和 client
的handlerChain
第一位。
上面这个Handler
完全符合Shadowsocks
的Rc4Md5
加密方式,即使用户使用弱密码也能得到满足条件的加密强度。
优缺点
无法识别数据流是否正确,即错误的数据流也能解密出错误的数据,但不能在第一时间发现数据是错误的。
这个问题会在Shadowsocks
后缀为GCM
的算法中解决。
但是这个Rc4
算法简单,在很多设备上都能拥有较快的处理速度。
更新2020-04-13
上面写的handler
有一点没考虑清除。真实使用场景下,接收到的io数据一般是存储在直接内存里的,上面读取到数组再解密的方式需要从直接内存拷贝到堆内存,这样会影响性能的。
更多情况下应该使用下面这个方法方法:
1
| public final int update(ByteBuffer var1, ByteBuffer var2) throws ShortBufferException {
|
直接操作Bytebuf
,提高性能减少GC。