一 简介

一次一密

对称加密算法:使用最广泛的流密码就是RC4

优点:密钥随机产生,只使用一次;无条件安全、加解密位加法运算效率高

缺点:要求密钥至少与明文一样长,密钥共享困难

img

流密码(序列密码)

  • 流密码基本思想:

密钥流发生器f产生zi=f(k,σ \sigmaσi),即种子密钥k产生密钥流z=z0z1z2…

加密y=y0y1y2…=Ez0(x0)Ez1(x1)Ez2(x2)…

  • 有内部记忆原件的为流密码,否则分组密码
  • 内部记忆原件状态σ \sigmaσi独立于明文的称同步流密码,否则自同步流密码
  • 同步流密码加密器=滚动密钥生成器+加密变换器
  • 二元加法流密码,加密变换yi=zi⨁ \bigoplus⨁xi
  • 一次一密是加法流密码原型,若zi=ki,则加法流密码就退化成一次一密
  • 密钥流序列性质:极大的周期、良好的统计特性、抗线性分析、抗统计分析
  • 一次一密的密钥长度和明文一样长,流密码不是,需要种子密钥通过密钥生成器产生密钥流

二 RC4算法原理

介绍

首先,RC4算法所依赖的最根本原理是:对明文使用同一个密钥异或两次最后仍是得到原文。即生成密钥之后,由密钥与明文异或后生成密文(加密过程),由同一密钥与密文异或后得到明文(解密过程)。

在介绍RC4算法原理之前,先看看算法中的几个关键变量:

1、密钥流:RC4算法的关键是根据明文和密钥生成相应的密钥流,密钥流的长度和明文的长度是对应的,也就是说明文的长度是500字节,那么密钥流也是500字节。当然,加密生成的密文也是500字节,因为密文第i字节=明文第i字节^密钥流第i字节;

2、状态向量S:长度为256,S[0],S[1]…..S[255]。每个单元都是一个字节,算法运行的任何时候,S都包括0-255的8比特数的排列组合,只不过值的位置发生了变换;

3、临时向量T:长度也为256,每个单元也是一个字节。如果密钥的长度是256字节,就直接把密钥的值赋给T,否则,轮转地将密钥的每个字节赋给T;

4、密钥K:长度为1-256字节,注意密钥的长度keylen与明文长度、密钥流的长度没有必然关系,通常密钥的长度趣味16字节(128比特)。

实现步骤

下面是对RC4算法具体实现的简单介绍:

img

1、先初始化状态向量S(256个字节,用来作为密钥流生成的种子1)

按照升序,给每个字节赋值0,1,2,3,4,5,6…..,254,255

img

2、初始密钥(由用户输入),长度任意。如果输入长度小于256个字节,则进行轮转,直到填满。例如输入密钥的是“1,2,3,4,5”, 那么填入的是1,2,3,4,5, 1,2,3,4,5, 1,2,3,4,5……..

img

由上述轮转过程得到256个字节的向量T(用来作为密钥流生成的种子2)。

3、开始对状态向量S进行置换操作(用来打乱初始种子1),按照下列规则进行:

从第零个字节开始,执行256次,保证每个字节都得到处理

img

4、最后是秘钥流的生成与加密。

img

上面四个步骤简单总结一下:

  • 第一步:生成了初始状态向量S
  • 第二步:输入初始密钥,生成临时向量T
  • 第三步:对状态向量S进行置换操作,打乱初始种子,生成S’
  • 第四步:使用明文和S‘生成最终加密的密钥流
  • 第五步:使用密钥流对明文进行异或,实现最终加密。

代码实现

具体实现代码如下:RC4加解密算法

解密按照前面写的,异或两次就是原文,所以只要把密钥流重新拿过来异或一次就能得到原文了

下面是调用python简单实现的上述所说:

def gen_S():
for i in range(256):
S.append(i)


def init_Key():
T = []
for i in range(256):
T.append(WordList[i % 62])
return T


def permute_S():
j = 0
for i in range(256):
j = (j + S[i] + ord(T[i])) % 256
temp = S[i]
S[i] = S[j]
S[j] = temp
return S


def enc_RC4():
i = 0
j = 0
num = 0
data = []
for i in range(len(text1)):
i = (i + 1) % 256
j = (j + S[i]) % 256
temp = S[i]
S[i] = S[j]
S[j] = temp
t = (S[i] + S[j]) % 256
data.append(chr((ord(text1[num]) ^ S[t])))
num += 1
print(data)


if __name__ == '__main__':
# 第一步
S = []
gen_S()
# 第二步
WordList = list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
T = init_Key()
# 第三步
permute_S()
# 第四步
# text = "12345"
# enc_RC4()
# 第五步
text1 = ['£', '7', 'w', '0', '\x16']
enc_RC4()

image.png

三 实现标准RC4算法

#include <jni.h>
#include <string>

using namespace std;

void rc4_init(char s[256], char *pwd, int len);

void rc4_enc(char s[256], char *data, int len);


void rc4_init(char S[256], char *K, size_t len) {
char T[256] = {0};
for (int i = 0; i < 256; ++i) {
S[i] = i;
T[i] = K[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 rc4_enc(char S[256], char *data, int len) {
int i , j = 0;
for (int k = 0; k < len; ++k) {
i = (i + 1) % 256;
j = (j + S[i]) % 256;
swap(S[i],S[j]);
int t = (S[i] + S[j]) % 256;
*(data + k) ^= S[t];
}
}

extern "C"
JNIEXPORT jstring JNICALL
Java_com_roysue_rc4_MainActivity_rc4(JNIEnv *env, jobject thiz, jbyteArray data) {
jbyte *t_data = env->GetByteArrayElements(data,0);
env->ReleaseByteArrayElements(data,t_data,0);
char *c_data = reinterpret_cast<char *>(t_data);
int len = env->GetArrayLength(data);

char S[256] = {0};
char *pwd = "123456";
rc4_init(S,pwd,len);
rc4_enc(S,c_data,len);
return env->NewStringUTF(c_data);
}

四 RC4算法汇编分析

image.png

上图中的代码是没有进行变量抽取之前的,可以看到函数名都还是可以看到的。

可以在代码中加上如下代码,进行变量抽取:

set (CMAKE_CXX_FLAGS "$(CMAKE_CXX_FLAGS" -fvisibility=hidden)

image.png

整个汇编代码基本和C的区别不大,这块就不细看了。。。。

RC4算法的特征有

  • 首先根据原理我们可以看到会初始化一个256字节的数组
  • 其次会将一个key也填充到数组中
  • 函数的话大概率都是两个参数,一个是key 一个是keylen

具体分析,可以见逆向分析中的密码学—RC4_rc4逆向-CSDN博客

五 总结

RC4算法本身实现并不复杂,只要熟悉其加密/解密流程,逆向分析过程中一般都能很好的进行识别。
分析其它标准加密算法基本也是同样的流程,对于自定义的一些加密算法就需要耐心的跟踪分析了。
另外对于标准加密算法,也可以借助PEID的“Krypto ANALyzer”插件,或者IDA的“FindCrypt2”插件进行识别,使用这些插件能够更好地提高工作效率。

一般不怎么会在渗透过程中遇到RC4算法的,主要不方便,还要求一次一密。。。