RC4加密原理
流密码RC4
在密码学中,RC4(来自Rivest Cipher 4的缩写)是一种串流加密法,密钥长度可变。它加解密使用相同的密钥,因此也属于对称加密算法
整体加密流程:
RC4的加密核心主要是一步异或(明文与秘钥流进行逐字节异或),主要算法在秘钥流的生成方面。首先初始化一个S盒(一个无符号字符型,大小为256的数组),根据秘钥key(可自定,也可用伪随机数生成)生成一个T向量(同样为无符号字符型256大小)
unsigned char s[256];
unsigned char t[256];
for(int i=0;i<256;i++){ //初始化S盒和t向量
s[i]=i;
t[i]=key[i%keylen]; //秘钥key及其长度
}
通过T向量打乱S盒
int j = 0;
for(int i=0;i<256;i++){
j=(j+s[i]+t[i])%256;
swap(s[i],s[j]); //根据t向量打乱s盒 ,交换是s[i]和是[j] 循环256次 S盒内大部分顺序被打乱
}
下一步,生成秘钥流,可以将秘钥流与明(密)文直接异或 ,或者存放到一个大小为mlen的无符号字符型数组中。
unsigned char k[mlen];//保存秘钥流,或者直接进行异或
int i=0; j=0; int tmp;
for(int index=0;index<mlen;index++){ //生成与明文长度一致的秘钥流
i=(i+1)%256; //明文长度可能超过256,不能直接i++
j=(j+s[i])%256;
swap(s[i],s[j]);
tmp=(s[i]+s[j])%256;
k[index]=s[tmp];//保存秘钥
}
最后将生成的秘钥流与明文进行逐字节异或
for(i=0;i<mlen;i++)
m[i]=m[i]^k[i];//主要进行了一步异或
分析上述过程,在生成秘钥流的过程中只有KEY参与,所以相同key生成的秘钥流一致,而最终只是秘钥流与明文进行了逐字节异或,根据异或的特征,故加密和解密公用一套算法。
同时,RC4加密的安全性与key的长度也有着关系,key太短,则容易被爆破,key太长(>256)则多余字节根本没有参与加密,一般key的选择在128字节左右就够用并且是安全的。
整体流程代码:
int main(){ //加密的主函数
unsigned char flag[]="需要加密的数据";
char key[]="秘钥";
RC4_encrypt(flag,key,sizeof(flag)-1,strlen(key));
for(int i=0;i<sizeof(flag)-1;i++)
cout<<(int)flag[i]<<",";
return 0;
}
void RC4_encrypt(unsigned char *m, char *key,int mlen,int keylen){
unsigned char s[256];
unsigned char t[256];
for(int i=0;i<256;i++){ //初始化s和t向量
s[i]=i;
t[i]=key[i%keylen];
}
int j = 0;
for(int i=0;i<256;i++){
j=(j+s[i]+t[i])%256;
swap(s[i],s[j]); //根据t向量打乱s盒
}
unsigned char k[mlen];//保存秘钥流,或者直接进行异或
int i=0; j=0; int tmp;
for(int index=0;index<mlen;index++){ //生成与明文长度一致的秘钥流
i=(i+1)%256;
j=(j+s[i])%256;
swap(s[i],s[j]);
tmp=(s[i]+s[j])%256;
k[index]=s[tmp];//保存秘钥
}
for(i=0;i<mlen;i++)
{
m[i]=m[i]^k[i];//主要进行了一步异或,加密的逆过程就是解密
}
}
c++/c中int和char类隐式转换十分方便(通过ASCII码),这样方便统计长度和进行格式转换。
C++代码中问题解决
在代码实现中可能的问题:
为什么要用unsigned char,与char同为 1个字节大小,但是整数的范围不同 unsigned char的范围才是我们要的0~255 而 单纯char的话第一位为符号位 所以为 -128 到 127,这里最好要用无符号char
另外就是在主函数中的调用:
unsigned char flag[]={109,0,85,138,95,219,19,35,59,20,97,8,73,15,81,201,242};
char key[]="keykeykey";
RC4_encrypt(flag,key,sizeof(flag),strlen(key));
cout<<flag<<endl;
return 0;
在解密过程中,密文数组中可能会有0,根据strlen的原理是遇到‘\0’就停止可能出现错误,并且密文数组定义为了unsigned char类型,不能用strlen函数来判断长度,所以这里用sizeof来代替,当然key数组最好也定义为unsigned char类型,这里便不再展示。
如果是{xx,xx}则sizeof表示正常大小,如果是“aaaa”,则sizeof表示长度为5(多了一个0)注意细节。
IDA逆向分析
完善主函数,生成的EXE文件拖入IDA64中分析。
简单写了一个对输入进行一个RC4加密之后和v7(如果IDA给v7开辟的int型数组,注意小端序)串进行比较,key直接给了。
初始化S盒和T盒与变异的RC4算法
RC4算法魔改
一般魔改的RC4主要是S盒和T盒的初始化方面,正常的初始化S盒是0-255 而T盒则是key循环填进去
曾经遇到过将S盒的初始化改为255-i,解密只需要以相同初始化方式即可
swap交换函数内部
接下来便是打乱S盒和生成秘钥流。
v8=v6有点诡异,但看他们在栈中的空间,v8是rsp+228h v6是rsp+20h,差值是208h为520故,v8和v6也是没有交集的空间,其余则是生成秘钥流和进行异或了。
IDA中的变量分配问题
可知IDA反编译对数组的处理,一般开辟的空间要比原空间大,并且可能产生一些不同,比如v7也就是secret,我们自定义的是17大小,而开辟了32个大小,并且unsigned char型变成了char型,所以v7看起来会有一些负数值。同样,key数组的大小和flag数组的大小都开辟的了大了许多,但参与运算的数量并没有变化。
源代码
实现上可能有些不足或缺陷,还望指正。
#include<iostream>
#include<cstring>
using namespace std;
void swap(int &a,int &b); //flag{This_is_RC4}
void RC4_encrypt(unsigned char *m, char *key,int mlen,int keylen);
int main(){
unsigned char flag[18];
cout<<"tell me your secret:";
cin>>flag;
int len=17;
char key[]="keykeykey";
RC4_encrypt(flag,key,len,strlen(key));
unsigned char secret[17]={109,0,85,138,95,219,19,35,59,20,97,8,73,15,81,201,242};
for(int i=0;i<len;i++){
if(secret[i]!=flag[i]){
cout<<"try again!"<<endl;
return 0;
}
}
cout<<"you get it!"<<endl;
return 0;
}
void swap(int &a,int &b){
int temp;
temp=a;
a=b;
b=temp;
}
void RC4_encrypt(unsigned char *m, char *key,int mlen,int keylen){
unsigned char s[256];
unsigned char t[256];
for(int i=0;i<256;i++){ //初始化s和t向量
s[i]=i;
t[i]=key[i%keylen];
}
int j = 0;
for(int i=0;i<256;i++){
j=(j+s[i]+t[i])%256;
swap(s[i],s[j]); //根据t向量打乱s盒
}
unsigned char k[mlen];//保存秘钥流,或者直接进行异或
int i=0; j=0; int tmp;
for(int index=0;index<mlen;index++){ //生成与明文长度一致的秘钥流
i=(i+1)%256;
j=(j+s[i])%256;
swap(s[i],s[j]);
tmp=(s[i]+s[j])%256;
k[index]=s[tmp];//保存秘钥
}
for(i=0;i<mlen;i++)
{
m[i]=m[i]^k[i];//主要进行了一步异或,加密的逆过程就是解密
}
}
参考
流密码和RC4:https://www.cnblogs.com/block2016/p/5601925.html
密码学基础和RC4:https://www.jianshu.com/p/f22a98eb437f
维基百科-RC4