一、引言
近期也是对加密算法也是做了一套详尽的学习,主要是前期学习过程中仅记忆了相关特征,并未进行深度研究,RC家族将以以下顺序展开:RC 家族全貌 → 再重点讲 RC4 原理、逆向识别、代码实现 → 再补 RC2/RC5/RC6 的结构特征与最小实现。
常见成员:
- RC2:分组密码
- RC4:流密码(最为常见)
- RC5:参数化分组密码
- RC6:RC5 的改进版,AES 候选算法之一
二、RC 家族总体区别
| 算法 | 类型 | 核心特点 | CTF中常见度 | 逆向识别难度 |
|---|---|---|---|---|
| RC2 | 分组密码 | 64-bit 分组,历史较老 | 低 | 中 |
| RC4 | 流密码 | 256字节状态数组,KSA+PRGA | 很高 | 低 |
| RC5 | 分组密码 | 参数化设计,数据相关循环移位 | 中 | 中高 |
| RC6 | 分组密码 | 在RC5基础上加入乘法,128-bit分组 | 中低 | 高 |
三、RC4
RC4是加解密可逆的加密算法,因此我们只讲述加密过程,RC4加密主要实现两个阶段:
KSA(Key Scheduling Algorithm,密钥调度)
- 初始化
S[0..255] - 用密钥不断打乱 S 盒
PRGA(Pseudo-Random Generation Algorithm,伪随机生成)
与明文异或得到密文
不断交换 S[i]、S[j]
输出密钥流字节
KSA
然后交换:
PRGA
交换:
输出索引:
密钥流字节:
最终:
这么说可能有些抽象,我们直接用一段代码来展示
def rc4_crypt(key: bytes, data: bytes) -> bytes:
# KSA
S = list(range(256))
j = 0
key_len = len(key)
for i in range(256):
j = (j + S[i] + key[i % key_len]) & 0xFF
S[i], S[j] = S[j], S[i]
# PRGA
i = 0
j = 0
out = bytearray()
for byte in data:
i = (i + 1) & 0xFF
j = (j + S[i]) & 0xFF
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) & 0xFF]
out.append(byte ^ k)
return bytes(out)
四、RC2(基本不考魔改)
RC2的加密方式我们不做详细研究,只需要知道RC2是DES的替代算法,仿照的还是DES的相关模式,明文和密文都是64bit构成,密钥长度为1bit到128bit,下面给出简单库函数调用方法
#ECB模式
from Crypto.Cipher import ARC2
from Crypto.Util.Padding import pad, unpad
key = b"12345678" # RC2 常见 8~16 字节
plaintext = b"hello rc2"
cipher = ARC2.new(key, ARC2.MODE_ECB)
ciphertext = cipher.encrypt(pad(plaintext, ARC2.block_size))
cipher2 = ARC2.new(key, ARC2.MODE_ECB)
decrypted = unpad(cipher2.decrypt(ciphertext), ARC2.block_size)
print("ciphertext:", ciphertext.hex())
print("decrypted :", decrypted)
#CBC模式
from Crypto.Cipher import ARC2
from Crypto.Util.Padding import pad, unpad
key = b"12345678"
iv = b"12345678"
plaintext = b"hello rc2 cbc"
cipher = ARC2.new(key, ARC2.MODE_CBC, iv=iv)
ciphertext = cipher.encrypt(pad(plaintext, ARC2.block_size))
cipher2 = ARC2.new(key, ARC2.MODE_CBC, iv=iv)
decrypted = unpad(cipher2.decrypt(ciphertext), ARC2.block_size)
print("ciphertext:", ciphertext.hex())
print("decrypted :", decrypted)
五、RC5|RC6
RC5 最关键的标签有三个:
w:字长r:轮数b:密钥长度(字节)
如果反编译代码中大量出现 ROL/ROR,而且移位位数不是常数,而是由另一个寄存器或变量决定,那么很可能要往 RC5/RC6 方向想。
RC5 逆向识别点
- 常见魔数:
0xB7E151630x9E3779B9
- 分组通常是两个字(如两个 32 位)
- 每轮结构很像:
RC6 可以说是“RC5 加强版”。
RC6采用128-bit 分组
用四个寄存器:
A, B, C, D
大量循环移位
还会出现乘法:
#RC5
def rol(x, r, w=32):
mask = (1 << w) - 1
r &= (w - 1)
return ((x << r) & mask) | (x >> (w - r))
def ror(x, r, w=32):
mask = (1 << w) - 1
r &= (w - 1)
return (x >> r) | ((x << (w - r)) & mask)
def rc5_key_schedule(key: bytes, w=32, r=12):
# 这里只写最常用的 w=32
if w != 32:
raise ValueError("This minimal implementation uses w=32 only.")
P = 0xB7E15163
Q = 0x9E3779B9
u = w // 8
c = max(1, (len(key) + u - 1) // u)
L = [0] * c
# 按 little-endian 装入 L
for i in range(len(key) - 1, -1, -1):
L[i // u] = ((L[i // u] << 8) + key[i]) & 0xFFFFFFFF
t = 2 * (r + 1)
S = [0] * t
S[0] = P
for i in range(1, t):
S[i] = (S[i - 1] + Q) & 0xFFFFFFFF
A = B = i = j = 0
for _ in range(3 * max(c, t)):
A = S[i] = rol((S[i] + A + B) & 0xFFFFFFFF, 3, w)
B = L[j] = rol((L[j] + A + B) & 0xFFFFFFFF, (A + B), w)
i = (i + 1) % t
j = (j + 1) % c
return S
def rc5_encrypt_block(block: bytes, S, w=32, r=12):
if len(block) != 8:
raise ValueError("RC5-32 block size must be 8 bytes.")
A = int.from_bytes(block[:4], "little")
B = int.from_bytes(block[4:], "little")
A = (A + S[0]) & 0xFFFFFFFF
B = (B + S[1]) & 0xFFFFFFFF
for i in range(1, r + 1):
A = (rol(A ^ B, B, w) + S[2 * i]) & 0xFFFFFFFF
B = (rol(B ^ A, A, w) + S[2 * i + 1]) & 0xFFFFFFFF
return A.to_bytes(4, "little") + B.to_bytes(4, "little")
def rc5_decrypt_block(block: bytes, S, w=32, r=12):
if len(block) != 8:
raise ValueError("RC5-32 block size must be 8 bytes.")
A = int.from_bytes(block[:4], "little")
B = int.from_bytes(block[4:], "little")
for i in range(r, 0, -1):
B = ror((B - S[2 * i + 1]) & 0xFFFFFFFF, A, w) ^ A
A = ror((A - S[2 * i]) & 0xFFFFFFFF, B, w) ^ B
B = (B - S[1]) & 0xFFFFFFFF
A = (A - S[0]) & 0xFFFFFFFF
return A.to_bytes(4, "little") + B.to_bytes(4, "little")
if __name__ == "__main__":
key = bytes(range(16)) # 16字节密钥
pt = b"12345678" # 8字节分组
S = rc5_key_schedule(key, w=32, r=12)
ct = rc5_encrypt_block(pt, S, w=32, r=12)
dt = rc5_decrypt_block(ct, S, w=32, r=12)
print("pt:", pt)
print("ct:", ct.hex())
print("dt:", dt)
#RC6
def rol(x, r, w=32):
mask = (1 << w) - 1
r &= (w - 1)
return ((x << r) & mask) | (x >> (w - r))
def ror(x, r, w=32):
mask = (1 << w) - 1
r &= (w - 1)
return (x >> r) | ((x << (w - r)) & mask)
def rc6_key_schedule(key: bytes, w=32, r=20):
if w != 32:
raise ValueError("This minimal implementation uses w=32 only.")
P = 0xB7E15163
Q = 0x9E3779B9
u = w // 8
c = max(1, (len(key) + u - 1) // u)
L = [0] * c
for i in range(len(key) - 1, -1, -1):
L[i // u] = ((L[i // u] << 8) + key[i]) & 0xFFFFFFFF
t = 2 * r + 4
S = [0] * t
S[0] = P
for i in range(1, t):
S[i] = (S[i - 1] + Q) & 0xFFFFFFFF
A = B = i = j = 0
for _ in range(3 * max(c, t)):
A = S[i] = rol((S[i] + A + B) & 0xFFFFFFFF, 3, w)
B = L[j] = rol((L[j] + A + B) & 0xFFFFFFFF, (A + B), w)
i = (i + 1) % t
j = (j + 1) % c
return S
def rc6_encrypt_block(block: bytes, S, w=32, r=20):
if len(block) != 16:
raise ValueError("RC6 block size must be 16 bytes.")
A = int.from_bytes(block[0:4], "little")
B = int.from_bytes(block[4:8], "little")
C = int.from_bytes(block[8:12], "little")
D = int.from_bytes(block[12:16], "little")
B = (B + S[0]) & 0xFFFFFFFF
D = (D + S[1]) & 0xFFFFFFFF
lgw = 5 # log2(32)
for i in range(1, r + 1):
t = rol((B * ((2 * B + 1) & 0xFFFFFFFF)) & 0xFFFFFFFF, lgw, w)
u = rol((D * ((2 * D + 1) & 0xFFFFFFFF)) & 0xFFFFFFFF, lgw, w)
A = (rol(A ^ t, u, w) + S[2 * i]) & 0xFFFFFFFF
C = (rol(C ^ u, t, w) + S[2 * i + 1]) & 0xFFFFFFFF
A, B, C, D = B, C, D, A
A = (A + S[2 * r + 2]) & 0xFFFFFFFF
C = (C + S[2 * r + 3]) & 0xFFFFFFFF
return (
A.to_bytes(4, "little") +
B.to_bytes(4, "little") +
C.to_bytes(4, "little") +
D.to_bytes(4, "little")
)
def rc6_decrypt_block(block: bytes, S, w=32, r=20):
if len(block) != 16:
raise ValueError("RC6 block size must be 16 bytes.")
A = int.from_bytes(block[0:4], "little")
B = int.from_bytes(block[4:8], "little")
C = int.from_bytes(block[8:12], "little")
D = int.from_bytes(block[12:16], "little")
lgw = 5
C = (C - S[2 * r + 3]) & 0xFFFFFFFF
A = (A - S[2 * r + 2]) & 0xFFFFFFFF
for i in range(r, 0, -1):
A, B, C, D = D, A, B, C
u = rol((D * ((2 * D + 1) & 0xFFFFFFFF)) & 0xFFFFFFFF, lgw, w)
t = rol((B * ((2 * B + 1) & 0xFFFFFFFF)) & 0xFFFFFFFF, lgw, w)
C = ror((C - S[2 * i + 1]) & 0xFFFFFFFF, t, w) ^ u
A = ror((A - S[2 * i]) & 0xFFFFFFFF, u, w) ^ t
D = (D - S[1]) & 0xFFFFFFFF
B = (B - S[0]) & 0xFFFFFFFF
return (
A.to_bytes(4, "little") +
B.to_bytes(4, "little") +
C.to_bytes(4, "little") +
D.to_bytes(4, "little")
)
if __name__ == "__main__":
key = bytes(range(16))
pt = b"0123456789ABCDEF" # 16字节分组
S = rc6_key_schedule(key, w=32, r=20)
ct = rc6_encrypt_block(pt, S, w=32, r=20)
dt = rc6_decrypt_block(ct, S, w=32, r=20)
print("pt:", pt)
print("ct:", ct.hex())
print("dt:", dt)
Comments NOTHING