Base64 加密解密的 C 语言实现和 python 实现
由于逆向中涉及 base64
加密解密很多次了,每次都不能一眼看出来,所以自己用 python
和 C
语言各仿照着写了一遍流程,希望能给以后加深影响。
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
char *base64_encode(char *str) //接收要加密的字符串
{
int str_len; //获取输入的字符的长度,用于后续是否是3的倍数的操作
int len; //不足3的倍数是直接下表添加'='
int i,j; //用于后续3位一组为单位拆分3*8为4*6的后续操作
char *encodestr; //定义用于接受加密后的字符串
char *base64_table="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; //定义base64的基本表单元素,用指针是因为C语言的字符串没法像python一样可以直接对字符串进行非地址的截取。
str_len=strlen(str); //获取输入的字符串长度
if (str_len % 3 == 0)
len=(str_len/3)*4; //如果输入字符是3的倍数,就不用补0,那按照3拆4的规则,加密后的字符串的长度就是len
else
len=((str_len/3)+1)*4; //这里的str_len/3会取整数部分,如果输入的不是3的倍数,比如多一个就是4,多两个也是4。
encodestr=malloc(sizeof(char)*len+1); //因为C语言不像python一样可以直接+=来添加字符串,所以要用列表,这里是为列表赋予足够的加密字符空间,加1是因为后面的'\0'结束符。
encodestr[len]='\0'; //写入结束符
for(i=0,j=0;i<len-1;j+=3,i+=4) //开始3*8变4*6的拆分循环,i<len-1是因为最后一个字符是'\0',输入字符3个为1组。值得注意的是如果输入字符不是3的倍数,那么最后一组,str[j+1],str[j+2]中依旧会赋值,但是赋的值会被后面switch语句覆盖,所以结果没有影响。
{
encodestr[i]=base64_table[str[j]>>2]; //3*8变4*6的第一个拆分,base64_table前面说了是指针操作,所以可以按字符取。8位一个字符往右移动2位,剩余6位,得出第一个加密下标。
encodestr[i+1]=base64_table[(str[j]&0x3)<<4 | str[j+1]>>4]; //这里空间折叠可能更好理解,以前的做题经验中 '|' 可以让两边移位同时进行。这里str[j]&0x3取第一个8位的最后2位向前移动4位变成6位中头2位,同时str[j+1]>>4把自身8位中4位向右移动了,那么str[j]8位空间字符剩下2位在前头,str[j+1]8位空间中字符剩下4位在后头,所以合在一起就是6位。
encodestr[i+2]=base64_table[(str[j+1]&0xf)<<2 | str[j+2]>>6]; //同理,str[j+1]剩下4位向千移动2位变成6位中的头4位,str[j+2]>>6向右移动6位剩余两位,作为6位中的末尾2位。
encodestr[i+3]=base64_table[str[j+2]&0x3f]; //这里直接与0x3f取6位,不能用<<2,我也暂时不懂
}
switch(str_len%3) //用switch给不足3的最后一组赋值'=',来覆盖前面str[j+1]和str[j+2]生成的但是不改存在的字符。
{
case 1:
encodestr[i-2]='=';
encodestr[i-1]='=';
break;
case 2:
encodestr[i-1]='=';
break;
}
return encodestr;
}
char *base64_decode(char *str)//接收要解密的字符串
{
int table[]={0,0,0,0,0,0,0,0,0,0,0,0, //根据base64表,以字符找到对应的十进制数据 ,这里是int类型,移位的时候要转换成char地址。
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,62,0,0,0,
63,52,53,54,55,56,57,58,
59,60,61,0,0,0,0,0,0,0,0,
1,2,3,4,5,6,7,8,9,10,11,12,
13,14,15,16,17,18,19,20,21,
22,23,24,25,0,0,0,0,0,0,26,
27,28,29,30,31,32,33,34,35,
36,37,38,39,40,41,42,43,44,
45,46,47,48,49,50,51
};
int len;
int str_len;
char *decodestr;
int i,j;
len=strlen(str); //获取密文长度,必然是4的倍数。
if(strstr(str,"==")) //strstr函数获取字符串位置,要去除掉补加的'='的个数才是真正加密后的密文
str_len=len/4*3-2;
else if (strstr(str,"="))
str_len=len/4*3-1;
else
str_len=len/4*3;
decodestr=malloc(sizeof(char)*str_len+1);//因为C语言不像python一样可以直接+=来添加字符串,所以要用列表,这里是为列表赋予足够的加密字符空间,加1是因为后面的'\0'结束符。
decodestr[str_len]='\0'; //写入结束符,那么后面'='不用用全0替换了,因为'='高2位是0,不会影响。
for(i=0,j=0;i<len-1;j+=3,i+=4)//开始4*6变3*8的拆分循环,i<len-1是因为最后一个字符是'\0',输入字符4个为1组。值得注意的是就算最后有补位用的'=',但是由于'='高2位为0,而且前面有decodestr[str_len]='\0';的截断,所以'='最后不会影响解密结果。
{
decodestr[j]=((char)table[str[i]]) << 2 | ((char)table[str[i+1]]) >> 4; //4*6变3*8的第一个拆分,table是int地址,所以要转换为char地址才可以按字符取。按4位一组,str[i]的00xxxxxx的8位地址把前面2位0移掉,剩6位。str[i+1]的00xxxxxx后4位移动掉,剩下00xx,实质上是除去前面00只剩下xx两个字符,总共8个字符。
decodestr[j+1]=((char)table[str[i+1]]) << 4 | ((char)table[str[i+2]]) >> 2; //按4位一组,str[i+1]的00xxxxxx的8位地址把前面4位移掉,剩4位。str[i+2]的00xxxxxx后2位移动掉,剩下00xxxx,实质上是除去前面00只剩下xxxx4个字符,总共8个字符。
decodestr[j+2]=((char)table[str[i+2]]) << 6 | ((char)table[str[i+3]]); //按4位一组,str[i+2]的00xxxxxx的8位地址把前面6位移掉,剩2位。str[i+3]的00xxxxxx全部保留,实质上是除去前面00只剩下xxxxxx6个字符,总共8个字符。
}
return decodestr;
}
int main(int argc,char **argv)
{
char *buff; //接受加密或解密后的指向字符串的地址
if(strstr(argv[1],"-d")) //-d参数作为分隔
{
buff=base64_decode(argv[2]);
printf("%s",buff);
}
else
{
buff=base64_encode(argv[1]);
printf("%s",buff);
}
return 0;
}
.
.
用法:
gcc -o test 1.c
./test 1234567
/test -d MTIzNDU2Nw==
.
.
base64="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" #准备好base64的基表
def encryption(inputstring): #定义加密函数
ascii=['{:0>8}'.format(str(bin(ord(i))).replace('0b','')) for i in inputstring] #把每个输入字符保证8位一个,才能3*8变4*6。
#{:0>8}是右对齐8位然后左边补0,因为python是自己判断数据大小类型的,所以必须强制满足8位。bin转化二进制会带0b前缀,所以要用replace('0b','')去掉。
encrystr='' #while外的变量,返回base64加密后的字符串
equalnumber=0 #while外的变量,记录拆分后不足4的倍数时需要补齐的等号个数
while ascii:
subascii=ascii[:3] #用一个子列表subascii每次取输入的三位进行操作,前面操作后每位都是8位
while len(subascii)<3: #这里其实是最后一段截取中才会用上的,不满足3位时要单独取出,记录equalnumber数量用于后期补'='号,然后补齐8位的0免得干扰后面3*8拆分成4*6
equalnumber+=1 #计算要补‘=’的个数
subascii+=['0'*8] #补8个0来填充够3的倍数,这然后面就不会出错。
substring=''.join(subascii)#用substring合并subascii的3个8位,准备进行拆分操作
encrystringlist=[substring[x:x+6] for x in [0,6,12,18]] #开始进行3*8变4*6的拆分,每次拆分一组24位。
encrystringlist=[int(x,2) for x in encrystringlist] #把前面拆分的6位一组转成10进制,就不用进行位数补齐操作了,这是用来后面对应base64基表的下标。
if equalnumber:
encrystringlist=encrystringlist[0:4-equalnumber] #如果前面不足3字符补了0,比如2个8位字符16位,拆分后就要用3个6位共18位,所以有效位是4-equalnumber
encrystr+=''.join(base64[x] for x in encrystringlist) #这里encrystringlist已经在前面拆分成4*6且转换成10进制了,所以对应基表的下标。
ascii=ascii[3:] #每次向后取3个列表元素,对应while循环条件
encrystr+='='*equalnumber #因为前面encrystringlist[0:4-equalnumber]去掉了补0位,所以这里最后补齐'='号
return encrystr
def decryption(inputstring):
ascii=['{0:0>6}'.format(str(bin(base64.index(i))).replace('0b',''))for i in inputstring if i!='=']#从加密字符中取除补位'='之外加密字符,即6位生成的base64基表下标的数,按6位一组排列,准备拆分
decrystr=''#准备while外的解密后的字符
equalnumber=inputstring.count('=')#这里计数补位的'='号的个数,后面不够8位时会根据'='号补加位数。
while ascii:
subascii=ascii[:4]#取加密字符的4个6位一组共24位准备拆分合并成3*8
substring=''.join(subascii)#先连成一串24位
if len(substring)%8!=0:
substring=substring[0:-1*equalnumber*2]
#截取到倒数第equalnumber*2个元素。对不足8位的组补位,因为加密时1个8位要来2个6位,两个'='号,截取到8位就是倒数第4位1*2*2。2个8位要3个6位,要一个'='号,截取到16位就是倒数第2位1*1*2。
decrystringlist=[substring[x:x+8] for x in [0,8,16]]#开始进行4*6变3*8的拆分,每次拆分4个6位一组24位。
decrystringlist=[int(x,2) for x in decrystringlist if x]#把前面拆分的8位一组转成10进制,用来对应十进制ASCII码,if x功能不清楚,但不可缺少,应该是要排除空格吧。
decrystr+=''.join([chr(x) for x in decrystringlist])#这里decrystringlist已经在前面拆分成3*8且转换成10进制了,现在转换成ASCII码。
ascii=ascii[4:]#每次向后取4个列表元素,对应while循环条件
return decrystr
if __name__=="__main__":
print(encryption('a'))
print(decryption('YQ=='))
.
.
.
.
总结:
无