java rc4加密

Posted by hcy on April 3, 2020

本文主旨

Rc4是一种对称加密方式,且属于流加密,本文讲解如何使用JavaRc4的加密解密操作,并且讲解Shadowsocks是如何使用Rc4加密和Rc4md5加密的。

Rc4加密解密例子

Rc4需要一个密钥来加密。

下面这段代码演示了,如何使用Java自带的CipherRc4加密解密。密钥使用的是随机生成的

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
    /**
     * 测试java自带的rc4加密解密
     */
    @Test
    public void testRc4Origin() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException {
        //随机生成20位密码
        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));

        //因为是流加密,只要不关闭,encoder decoder对象可以一直使用
        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改进Rc4Rc4Md5算法

​ 之所以抛弃Rc4并不是Rc4本身加密强度不够,而是因为用户使用弱密码,所以只要增加密码位数就能得到安全的加密。

加强密钥

​ 既然用户会提供弱密码,那Shadowsocks就想办法将密钥加强,它的做法是:

1
realPassWord = md5password

​ 因为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");
        //对原始密钥取md5
        byte[] md5Password = md5.digest(originPassword);
        md5.reset();
        //重新加密密文 和 iv值得到真实密钥
        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;

/**
 * hcy 2020/4/3
 */
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;
    }

    /*
     * 第一次写数据时,生成随机IV 构建cipher
     * 使用cipher加密数据
     * */
    @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())));
    }


    /*
     * 初次读时,前16位作为iv使用,构建cipher,以后再使用则不需要构建
     * */
    @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添加到serverclienthandlerChain第一位。

​ 上面这个Handler完全符合ShadowsocksRc4Md5加密方式,即使用户使用弱密码也能得到满足条件的加密强度。

优缺点

​ 无法识别数据流是否正确,即错误的数据流也能解密出错误的数据,但不能在第一时间发现数据是错误的。

这个问题会在Shadowsocks后缀为GCM的算法中解决。

​ 但是这个Rc4算法简单,在很多设备上都能拥有较快的处理速度。

更新2020-04-13

​ 上面写的handler有一点没考虑清除。真实使用场景下,接收到的io数据一般是存储在直接内存里的,上面读取到数组再解密的方式需要从直接内存拷贝到堆内存,这样会影响性能的。

​ 更多情况下应该使用下面这个方法方法:

1
public final int update(ByteBuffer var1, ByteBuffer var2) throws ShortBufferException {

​ 直接操作Bytebuf,提高性能减少GC。


转载请注明出处:https://www.huangchaoyu.com/2020/04/03/java-rc4加密/