本文主旨
	Rc4是一种对称加密方式,且属于流加密,本文讲解如何使用Java做Rc4的加密解密操作,并且讲解Shadowsocks是如何使用Rc4加密和Rc4md5加密的。
Rc4加密解密例子
Rc4需要一个密钥来加密。
下面这段代码演示了,如何使用Java自带的Cipher做Rc4加密解密。密钥使用的是随机生成的
| 12
 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值,与上面得到的密钥进行混合,这样每个连接流使用的密钥就不同了。
| 12
 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 来将输出数据加密,输入数据解密。`
| 12
 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。