前言
这里主要是关于 RC4 加密的相关调研
之所以调研这个是因为之前存在一个问题, 调用 java 相关 api 进行 RC4 加密的结果 和调用 crypto-js 的结果不一样, 作为出发点开始
然后调用的 API 基于 hutool-5.4.0
然后 一部分基于 crypto-js 来进行分析
测试用例
这里是一个简单的 java 的测试用例, 有使用 RC4 进行加解密
RC4 是一种对称加密, 算法也是对称的
package com.hx.test02;import cn.hutool.crypto.symmetric.RC4;
import io.netty.buffer.ByteBufUtil;public class Test21Rc4Encode {// Test21Rc4Encodepublic static void main(String[] args) {String key = "11111";RC4 crypto = new RC4(key);byte[] srcBytes = "xxx".getBytes();byte[] encodedBytes = crypto.crypt(srcBytes);byte[] doubleEncodedBytes = crypto.crypt(encodedBytes);System.out.println(ByteBufUtil.hexDump(srcBytes));System.out.println(ByteBufUtil.hexDump(encodedBytes));System.out.println(ByteBufUtil.hexDump(doubleEncodedBytes));int x = 0;}}
RC4加密方式
传入的 msg 为原始字节序列
依次遍历原始字节序列 sourceByte, 从 box序列 中获取一个影响参数 argByte 来进行 异或 计算得到 targetByte
然后 原始字节序列 所有的字节处理完成之后 即为加密处理之后的字节序列
加解密方式是一样的是利用了如下特性 ((sourceByte ^ argByte) ^ argByte) = sourceByte
在多次 crypt 计算的过程中, 初始化 box序列 相同, i, j 计算方式相同, 在多次 crypt 计算的过程中, 第 i 个字节, 对应的 argByte 是相同的
对于 RC4 算法, 其实 box 序列是怎么样的已经不重要了, 只是一些 argByte 的 影响因素 而已
稍微看一下 box 序列的初始化
虽然这个 box 序列不太重要, 但是可以稍微深入的了解一下它的实现
大致的实现就是 box 初始化为 256 长度的字节序列, 然后按照索引进行初始化
然后 下面的初始化就是 进行一个遍历 box 序列 然后进行一个和传入 key 序列相关的一个 数据交换操作
得到一个 “乱序” 的 box 序列
crypto-js 版本的 RC4
初始化 box 序列的地方, 当然 在不同的上下文可能 定义不一样, 这里是按照 上面的 RC4 加密的过程来进行判断的
测试用例如下
<html><head><title>test</title></head><script type="text/javascript" src="crypto-js.min.js" ></script><script>// var md5 = CryptoJS.MD5("xxx");var key = "11111"var encrypted = CryptoJS.RC4.encrypt("xxx", key)console.log(encrypted.toString())var decrypted = CryptoJS.RC4.decrypt(encrypted.toString(), key).toString(CryptoJS.enc.Utf8)console.log(decrypted.toString())</script></html>
box 序列的初始化
这里也是 按照索引初始化, 然后按照遍历 box序列的 每一个字节 换换换
然后 循环每一个输入字节进行 异或操作
至于 pick 到的 n() 具体数字是什么不重要, 只要保证多次执行 输入序列的第i个字节 拿到的 argByte 是一样的即可
函数 n() 是 pick 四个字节的 argByte, 然后封装成 32 位的整数
this._i, this._j 保存的是上下文, 这里的算法细节 和 hutool-all 中的实现一致
可以发现的是当输入的 key 相同的情况下, 每次 上下文的 box 序列不相同, 这个又是怎么影响到的呢
_doReset 中 box 序列有一个 e 的影响因素, 这个是外部传入的
这个 e 来自于根据 key 计算的一个 r
这里根据 key 生成 序列的时候, 传入的 i 为 null, 设置为 random() 影响到了 计算的结果 r
从而继续向下传递 影响到了 _doReset 中的计算
crypto-js 版本的 RC4 的 ToString 的实现
转换的时候加了固定的 2 * 4 个字节, 添加了 t.words[表示 salt] 最后添加的才是 RC4 加密之后的结果
这里合计 2 * 4 + 2 * 4 + 3 = 19 字节, 然后进行 Base64 加密
t.words 来自于自于根据 key 计算的一个 r 的地方
同样和这个 随机因子有关系, 因此 他会变化
crypto-js 版本的 RC4解密
将加密之后的文本 base64 解密, 然后校验前八个字节 "Salted__"
然后 接着 八个字节作为 salt, 在 execute 中来作为影响 box 序列计算的 salt
然后 后面的流程就和 加密一样了, 计算 box 序列, 然后 按字节异或 获取解密之后的文本
另外还有一个 java版本 实现的 crypto-js 解密, 实现思路 貌似不同
如何使用Java从CryptoJS解密加密的AES-256字符串?
完