RC4

RC4加密算法

不花大把时间介绍历史来源,想要了解的可以去看维基百科

RC4的学习难度较低,逆向的难度同样较低,建议当为初级算法来学习

基本介绍

RC4(来自Rivest Cipher 4的缩写)是一种串流加密法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法。

RC4是有线等效加密(WEP)中采用的加密算法,也曾经是TLS传输层安全协议可采用的算法之一。

2015年由 RFC 7465 禁止RC4在所有版本的TLS中使用。

加解密原理

RC4 由伪随机数生成器和异或运算组成(由于异或运算的对合性,RC4 加密解密使用同一套算法)。RC4 的密钥长度可变,范围是[1,255]。RC4 一个字节一个字节地加解密。给定一个密钥,伪随机数生成器接受密钥并产生一个 S 盒。S 盒用来加密数据,而且在加密过程中 S 盒会变化。

算法构成

RC4的核心在于其对于S-Box的处理上

RC4算法的核心主要由两部分组成

1.密钥调度算法(KSA)

2.伪随机生成算法(PRGA)

结合实际代码实现进行分析,这里使用的是python代码,因为其较为简单易懂

先展示初始化部分

def sbox_init(key: bytes) -> list:
    """
    RC4 初始化(KSA)
    根据密钥生成 S 盒
    """
    S = list(range(256))
    j = 0

    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) % 256
        S[i], S[j] = S[j], S[i]#交换S[i]和S[j]

    return S

初始化部分使用了KSA来打乱S盒子,可以看到

j = (j + S[i] + key[i % len(key)]) % 256

这里对j进行了处理,使得S盒与密钥有关,并且这里使用密钥不断交换 S 中元素的位置,最终打乱了S盒,人类很难还原。

我们可以说j让s盒是随机的,i保证了s盒的每个元素都得到了处理(因为遍历了S[i]进行交换处理)

这里需要已知的变量是key以及S盒的初始值,有key才能对S盒进行打乱,S盒的初始值同样也会影响打乱的结果

RC4中S盒的打乱过程通常是伪随机的,而且一般是如上的固定算法,不再赘述了

由于逆向工程中我们面对的常常是对输入数据进行处理后再与密文进行比对的逻辑,我们先展示一遍RC4的加密部分,不过事实上RC4的加密部分与解密部分十分相似,这就是对称加密算法的特质

加密部分

def rc4_encrypt(S, data):
    """
    RC4 加密/解密函数(PRGA)
    S:初始化后的 S 盒
    data:待加密数据(bytes)
    """
    i = 0
    j = 0
    result = bytearray()

    for byte in data:
        # 更新 i、j
        i = (i + 1) % 256
        j = (j + S[i]) % 256

        # 交换 S[i] 和 S[j]
        S[i], S[j] = S[j], S[i]

        # 生成密钥流字节
        k = S[(S[i] + S[j]) % 256]

        # 与明文异或
        result.append(byte ^ k)

    return bytes(result)

k是我们临时生成的密钥流字节,这里k其实就是伪随机生成算法(PRGA)的结果

    i = (i + 1) % 256
    j = (j + S[i]) % 256
    # 交换 S[i] 和 S[j]
    S[i], S[j] = S[j], S[i]

    # 生成密钥流字节
    k = S[(S[i] + S[j]) % 256]

要进行逆向,其实得到k就行了,其实这一长串都是为了得到k,这个过程就是伪随机生成算法(PRGA)

下面展示下解密函数

def rc4_decrypt(S, data):
    """
    RC4 解密函数(PRGA)
    S:初始化后的 S 盒
    data:待解密数据(bytes)
    """
    i = 0
    j = 0
    result = bytearray()

    for byte in data:
        # 更新 i、j
        i = (i + 1) % 256
        j = (j + S[i]) % 256

        # 交换 S[i] 和 S[j]
        S[i], S[j] = S[j], S[i]

        # 生成密钥流字节
        k = S[(S[i] + S[j]) % 256]

        # 与密文异或得到明文
        result.append(byte ^ k)

    return bytes(result)

可以看到这里其实与加密函数完全一致!

这是为什么呢?最重要的地方就在于最后一步

result.append(byte ^ k)

其实本质是都是密文与k进行异或得到了明文,又或者明文与k进行了异或得到了密文

因为异或具有对合性,对同一个值进行异或的过程是可逆的,比如说我将原文与0x90进行异或,得到了新的数据,再将这个新的数据异或回去,就能得到原文

所以RC4是一种比较简单的算法了

好了你已经掌握RC4了,可以开始逆向工程了(大树守卫左边走)

逆向工程

[SWPUCTF 2021 新生赛]简简单单的解密

题目名称:[SWPUCTF 2021 新生赛]简简单单的解密

题目来源NSSCTF

题目标签:RC4,逆向工程,python

下载附件,得到原文件,是一个python文件,查看代码

import base64,urllib.parse
key = "HereIsFlagggg"
flag = "xxxxxxxxxxxxxxxxxxx"

s_box = list(range(256))
j = 0


for i in range(256):
    j = (j + s_box[i] + ord(key[i % len(key)])) % 256
    s_box[i], s_box[j] = s_box[j], s_box[i]
res = []
i = j = 0


for s in flag:
    
    i = (i + 1) % 256
    j = (j + s_box[i]) % 256
    s_box[i], s_box[j] = s_box[j], s_box[i]
    t = (s_box[i] + s_box[j]) % 256
    k = s_box[t]
    res.append(chr(ord(s) ^ k))



cipher = "".join(res)
crypt = (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
enc = str(base64.b64decode(crypt),'utf-8')
enc = urllib.parse.quote(enc)
print(enc)
# enc = %C2%A6n%C2%87Y%1Ag%3F%C2%A01.%C2%9C%C3%B7%C3%8A%02%C3%80%C2%92W%C3%8C%C3%BA

可以看到关键地方

j = (j + s_box[i] + ord(key[i % len(key)])) % 256

这里是KSA

i = (i + 1) % 256
j = (j + s_box[i]) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
t = (s_box[i] + s_box[j]) % 256
k = s_box[t]

PRGA部分

由此可以看出这就是一个RC4加密算法,根据RC4算法的特性,同时也是对称加密算法的特性,我们可以对上面的代码进行复用,写一个简单的解密函数

对密文还进行了几次处理,这里需要注意,我们同样需要逆向回去

cipher = "".join(res)
crypt = (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
enc = str(base64.b64decode(crypt),'utf-8')
enc = urllib.parse.quote(enc)

密文先是被保存为了字符str(),然后进行了UTF-8编码,第三步进行了base64编码base64.b64encode(),但是由于它后面进行了base64解码base64.b64decode()所以无意义

然后quote,把UTF-8字节转换为了URL编码

所以逆向过程是把enc转换回UTF-8编码形式,再转换为str形式,然后切换为比特形态,就可以进行处理了

当然也不必那么麻烦,我们只需要将URL编码解码,使用ord()转换URL解码后的数据就可以了

ord()将一个字符转换为对应的 Unicode(ASCII)整数值。

这样密文能直接参与到异或中了,而它本身数据没发生改变,所以结果也不会发生改变

完整的EXP(具体攻击代码)

import base64,urllib.parse
key = "HereIsFlagggg"
flag = "xxxxxxxxxxxxxxxxxxx"

s_box = list(range(256))
j = 0
for i in range(256):
    j = (j + s_box[i] + ord(key[i % len(key)])) % 256
    s_box[i], s_box[j] = s_box[j], s_box[i]
res = []
i = j = 0

enc ="%C2%A6n%C2%87Y%1Ag%3F%C2%A01.%C2%9C%C3%B7%C3%8A%02%C3%80%C2%92W%C3%8C%C3%BA"
cipher = urllib.parse.unquote("%C2%A6n%C2%87Y%1Ag%3F%C2%A01.%C2%9C%C3%B7%C3%8A%02%C3%80%C2%92W%C3%8C%C3%BA")

for s in cipher:
    i = (i + 1) % 256
    j = (j + s_box[i]) % 256
    s_box[i], s_box[j] = s_box[j], s_box[i]
    t = (s_box[i] + s_box[j]) % 256
    k = s_box[t]
    res.append(chr(ord(s) ^ k))

print(res)
#flag就请自己输出一下啦

最后得到了flag

由此可见RC4的逆向工程也十分简单,代码的复用性很高

如果你觉得本篇文章有帮助,请给我点个赞,如果文章中存在错误、以及需要改进的地方,欢迎在评论指出或是私聊作者

posted @ 2026-08-04 11:37  DrD1Di  阅读(2)  评论(0)    收藏  举报