RC4加密算法学习

为了水博客不择手段(其实是ida自动绕反调试的插件没安上做不动NCTF了,呜呜呜)

tea已经水过了,那就水一篇rc4吧(

简介

RC4(来自Rivest Cipher 4的缩写)是一种流加密算法,密钥长度可变。它的加解密使用相同的密钥,因此也属于对称加密算法。RC4是有线等效加密(WEP)中采用的加密算法,也曾经是TLS可采用的算法之一。其最重要的实现是初始化算法和伪随机子密码的生成。
RC4的实现是以字节流的方式依次加密明文中的每一个字节,解密的时候也是依次对密文中的每个字节解密。(只有异或操作和S盒,所以加解密的过程相同,这是其可逆性的原理)

算法实现

先贴一份c++实现的代码

void init(int *s, char *key, int len)
{
	int t[256] = {0};
	char tmp = 0;
	for (int i = 0; i < 256; ++i)
	{
		s[i] = i;
		t[i] = key[i % len];
	}
	int j = 0;
	for (int i = 0; i < 256; ++i)
	{
		j = (j + s[i] + t[i]) % 256;
		swap(s[i], s[j]);
	}
}
void crypt(int *s, char *data, int len)
{
	int i = 0, j = 0, t = 0;
	char tmp;
	for (int k = 0; k < len; ++k)
	{
		i = (i + 1) % 256;
		j = (j + s[i]) % 256;
		swap(s[i], s[j]); 
		t = (s[i] + s[j]) % 256;
		data[k] ^= s[t];
	}
}

初始化算法(KSA)

先来看看初始化算法,此处我们假设S盒的长度是256,密钥key的长度为len且key的内容由用户规定,其长度的可变范围不限(超过256的不要就行)

void init(int *s, char *key, int len)
{
	int t[256] = {0};
	char tmp = 0;
	for (int i = 0; i < 256; ++i)
	{
		s[i] = i;
		t[i] = key[i % len];
	}
	int j = 0;
	for (int i = 0; i < 256; ++i)
	{
		j = (j + s[i] + t[i]) % 256;
		swap(s[i], s[j]);
	}
}

初始化算法的步骤为:

  • 初始化S盒为0-255
  • 扩展密钥长度,具体操作是:若key的长度小于256,则不断将其复制进临时数组t,直到t的长度刚好达到256(多的就不要了)
  • 根据t数组打乱S盒,使S盒近似为一个随机生成的、内含元素只有0-255的数组
    在该算法中,i确保了S盒的每个元素都被处理过,而j确保了这种打乱的处理是近似于随机的

伪随机子密码生成算法(PRGA)

void crypt(int *s, char *data, int len)
{
	int i = 0, j = 0, t = 0;
	char tmp;
	for (int k = 0; k < len; ++k)
	{
		i = (i + 1) % 256;
		j = (j + s[i]) % 256;
		swap(s[i], s[j]); 
		t = (s[i] + s[j]) % 256;
		data[k] ^= s[t];
	}
}

该算法对data的每一位都进行了加密,具体操作是通过一定的算法找到了S盒中的某一元素,并与data当前字节异或,得到密文。
值得注意的是,data保存为明文时,加密后得到密文;data保存为密文时,加密后得到明文。

实现

#include <bits/stdc++.h>
using namespace std;
void init(int *s, char *key, int len)
{
	int t[256] = {0};
	char tmp = 0;
	for (int i = 0; i < 256; ++i)
	{
		s[i] = i;
		t[i] = key[i % len];
	}
	int j = 0;
	for (int i = 0; i < 256; ++i)
	{
		j = (j + s[i] + t[i]) % 256;
		swap(s[i], s[j]);
	}
}
void crypt(int *s, char *data, int len)
{
	int i = 0, j = 0, t = 0;
	char tmp;
	for (int k = 0; k < len; ++k)
	{
		i = (i + 1) % 256;
		j = (j + s[i]) % 256;
		swap(s[i], s[j]); 
		t = (s[i] + s[j]) % 256;
		data[k] ^= s[t];
	}
}
char data[2333] = "flag{this_is_a_sample_flag}";
char key[2333] = "Hello_RC4";
int s[260];
int main()
{
//	freopen("test.data", "r", stdin);
//	freopen("test.data", "w", stdout);
//	cin >> data; 
	int len = strlen(key);
	init(s, key, len);
	len = strlen(data);
	crypt(s, data, len);
	cout << data;
	return 0;
}

当我们执行加密时,取消代码中stdout的注释,可以看到输出为

然后我们执行解密,取消代码中stdin和cin的注释,可以看到输出为

显然是乱码没读入好导致的(

一个在网上嫖的python实现的轮子

从上面的实现可以看到c++实现的局限性:遇到这种乱码可能解密不全
所以还是用hex值来加解密好一些,乱码什么的搞出来可能还得爆破一次(
为什么我不自己写呢?因为我不会python呜呜呜

# -- coding: utf-8 --
class RC4:
    def __init__(self, k):
        self.Sbox = self.RC4_init(k)  # 构造生成S盒

    def RC4_init(self, k):
        Sbox = [] * 255
        T = [] * 256
        for i in range(256):
            Sbox.append(i)  # 初始化 Sbox放入0-255数
            T.append(ord(k[i % len(k)]))  # 存放轮转的256位key
        j = 0
        for i in range(256):
            j = (j + Sbox[i] + T[i]) % 256
            Sbox[i], Sbox[j] = Sbox[j], Sbox[i]
        return Sbox

    def RC4_crypt(self, m):
        c = ''
        i = j = 0
        S = self.Sbox
        for n in range(len(m)):
            i = (i + 1) % 256
            j = (j + S[i]) % 256
            S[i], S[j] = S[j], S[i]  # 交换 更新Sbox
            t = (S[i] + S[j]) % 256
            c += '%02x' % (ord(m[n]) ^ S[t])  # keystream
        return c


# 加密过程
key = 'Hello_RC4'
rc4 = RC4(key)
# data='5bfe81e7151b1bb2d99eb9571c1aa73121c93215ae7f7b4c8dd944'.decode('hex')
# data = 'flag{this_is_a_sample_flag}'
data = 'flag{this_is_a_sample_flag}'
c = rc4.RC4_crypt(data)
print(c)

# 解密过程
key = 'Hello_RC4'
rc4 = RC4(key)
c = rc4.RC4_crypt(c.decode('hex'))
print(c.decode('hex'))

# 记一下:这轮子是py2环境的,记得用在线ide跑

运行结果:(第一行是密文的hex值)

例题:[DNUICTF 2021] Remember Crypt 4

这题名字提醒的很明显了:是个RC4的题,现在刚好拿出来复现一下

首先观察函数sub_140001120

刚学完RC4的我们显然的发现这是RC4的KSA操作

其中,v9是S盒,并且这里不存在临时数组t,而是由v6充当i % len(a3就是len),在第二次循环里现算现用。
然后是函数sub_140001240

也很显然是RC4的PRGA操作,不再赘述

最后,加密后的v10与0x22异或后与byte_14013B000数组相等,key是12345678abcdefghijklmnopqrspxyz,拿出byte数组的值,异或了后扔进C++版的RC4解密,输出就得到了flag:
flag{nice_to_meet_you}

posted @ 2021-12-23 15:30  iPlayForSG  阅读(670)  评论(0编辑  收藏  举报