base64编码解码中的问题及思考

base64中遇到的问题及解决方案
第一段代码是我的复制,来源于

[https://qianfei11.github.io/2018/05/12/C%E8%AF%AD%E8%A8%80%E5%AE%9E%E7%8E%B0Base64%E8%A7%A3%E5%AF%86%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/

#include<cstdio>
#include<cstdlib>
#include<cstring>
#define MAX 100

const char *base64payload = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

int find_pos(char c)
{
    for(int i = 0; i < 65; i++)
        if(c == base64payload[i])
            return i;
    return -1;
}

unsigned char *base64_encode(const char *s, const int len)
{
    unsigned int sign = len % 3;
    unsigned int res_len = len % 3 ? ((len) / 3 + 1) * 4 : (len) / 3 * 4;
    unsigned int i = 0, j = 0;
    unsigned char *res = (unsigned char *)malloc(res_len + 1);
    memset(res, 0, res_len + 1);
    for(i = 0, j = 0; i < len; i += 3, j += 4)
    {
        if(i + 2 >= len)
        {
            res[j] = (s[i] >> 2) & 0x3F;
            if(sign == 1)//因为只多出一个数据,故需要补两个'='
            {
                res[j + 1] = ((s[i] & 0x03) << 4) & 0x3F;
                res[j + 2] = 0x40;//0x40对应数字64,即为base64payload中下标为64的'='
                res[j + 3] = 0x40;
            }
            else if(sign == 2)//因为多出两个数据,故需要补一个'='
            {
                res[j + 1] = (((s[i] & 0x03) << 4) | ((s[i + 1] >> 4) & 0x0F));
                res[j + 2] = ((s[i + 1] & 0x0F) << 2) & 0x3F;
                res[j + 3] = 0x40;
                break;
            }
        }
        res[j] = (s[i] >> 2) & 0x3F;//取第一个字节的前六位,最高两位补零
        res[j + 1] = (((s[i] & 0x03) << 4) | ((s[i + 1] >> 4) & 0x0F));//取第一个字节的最后两位和第二个字节的前四位,最高两位补零
        res[j + 2] = (((s[i + 1] & 0x0F) << 2) | ((s[i + 2] >> 6) & 0x03));//取第二个字节的后四位和第三个字节的前两位,最高两位补零
        res[j + 3] = (s[i + 2] & 0x3F);//取第三个字节的最后六位,最高两位补零
    }
    for(j = 0; j < res_len; j++)
        res[j] = base64payload[res[j]];
    return res;
}

unsigned char *base64_decode(const char *s, const int len)
{
    unsigned int res_len = len / 4 * 3;
    unsigned int i = 0, j = 0;
    unsigned char *res = (unsigned char *)malloc(res_len + 1);
    memset(res, 0, res_len + 1);
    int count = len / 4;
    for(i = 0; i < count; i++)
    {
        int s_index = i * 4;
        int res_index = i * 3;
        int buffer[4];//四个字节一组存入
        int sign = 0;
        for(j = 0; j < 4; j++)
            buffer[j] = find_pos(s[s_index + j]);
        if(i == count - 1)
            for(j = 0; j < 4; j++)
                if(buffer[j] == 0x40)//如果为'=',sign的值加一
                    sign++;
        res[res_index] = ((buffer[0] & 0x3F) << 2 | (buffer[1] & 0x30) >> 4);
        if(sign == 2)
            break;
        res[res_index + 1] = ((buffer[1] & 0x0f) << 4 | (buffer[2] & 0x3C) >> 2);
        if(sign == 1)
            break;
        res[res_index + 2] = ((buffer[2] & 0x03) << 6 | (buffer[3] & 0x3F));
    }
    return res;
}

int main()
{
    unsigned char s1[MAX];
    scanf("%s", s1);
    unsigned char *s2 = base64_encode((const char *)s1, strlen((const char *)s1));
    printf("%s\n", s2);
    unsigned char *s3 = base64_decode((const char *)s2, strlen((const char *)s2));
    printf("%s\n", s3);
    free(s2);
    free(s3);
    return 0;
}

  1. 条件运算符: ? 带三个运算对象的运算符称为三元运算符, :条件运算符是C中唯一的三元运算符。通用形式:expression?expression2:expression3解释:如果expression1为真(非0),那么整个条件表达式的值与expression2的值相同;如果expression1为假(0),那么整个条件表达式的值与expression3 相同。int是有符号类型,占用4byte,32bit,int a=1,a的二进制需要整理的问题:
    1. 位操作(unsigned,int)
    2. malloc的使用
    3. C函数
    4. const限定符,指针

先来一波感想吧,这个base64谁玩谁知道,在我做这个的时候一般都是这样的状态:woc!我做出来了,我加密成功了!??woc,为什么只能加密3个,多了就加载不了?为什么加载的程序在输入字符超过11个后就会崩溃?…等等一些列的bug让我的心情经常处于高峰和低谷的切换状态,当时真的爽成弟弟了。依稀记得跟天语姐姐装b的时候,以为自己真的完工了,果然到了下午输入了超过11个英文字符的时候我发现居然崩溃了!我的天?于是硬着头皮,凭着我强大的心理素质硬是继续debug,不过效果也很明显,没有成功就对了:) 等我吐槽到这里的时候,当年崔万志说的话仿佛来到了我的耳边:抱怨没有用,一切靠自己。(但是吐槽出来感觉好多了啊哈哈哈哈) 好的,我闭嘴好好写总结。首先,我先把自己借鉴和原创的代码部分粘贴上 ?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 100

const char *base64words="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

//base64的编码运算
unsigned char *base64_encode(const char *source,const int len)
{
	unsigned int more_word=len%3;//判断是否为3的倍数字符
	unsigned int base64_len;//base64_len为编码后的字符长度


	if(more_word==0)//通过多余字节
		base64_len=len/3*4;
	else
		base64_len=(len/3+1)*4;

	unsigned int i=0,j=0;

	//开辟以长度为base64_len+1长度的字符空间,并把首地址赋给base64_code
	unsigned char *base64_code=(unsigned char *)malloc(base64_len+1);
	memset(base64_code,0,base64_len+1);//初始化开辟的空间

	for(i=0,j=0;i<len;i+=3,j+=4)//每次源代码前进3个字符,base64_code前进4个字符
	{
		if(i+2>=len)
		{
			base64_code[j]=(source[i]>>2)&0x3f;//开辟后的第一个字符不受影响
			if(more_word==1)
			{	//question1 并且这里也可以用另一种方式改写;
				base64_code[j+1]=((source[i]&0x3)<<4)&0x30;
				base64_code[j+2]=0x40;
				base64_code[j+3]=0x40;
			}

			if(more_word==2)
			{
				base64_code[j+1]=(((source[i]&0x3)<<4)|((source[i+1]>>4)&0x3f));
				base64_code[j+2]=((source[i+1]&0x3f)<<2)&0x3c;
				base64_code[j+3]=0x40;
			}
		}

		else
		{
			base64_code[j]=(source[i]>>2)&0x3f;
			base64_code[j+1]=(((source[i]&0x3)<<4)|((source[i+1]>>4)&0xf));
			base64_code[j+2]=(((source[i+1]&0xf)<<2)|((source[i+2]>>6)&0x3));
			base64_code[j+3]=(source[i+2])&0x3f;
		}
	}
	for(j=0;j<base64_len;j++)
		base64_code[j]=base64words[base64_code[j]];//perfect!!
	//base64_code[]中存储的是不是字符吗question2
		return base64_code;
}

//base64的解码运算

unsigned char *base64_decode(int *base64_code_num,int base64_len)
{
	int len=base64_len/4*3;
	int coun=base64_len/4;
	unsigned char *source=(unsigned char *)malloc(len+1);
	memset(source,0,len+1);
	unsigned int i=0,j=0,k;

	//unsigned int *base64_code_num=get_num(base64_code,base64_len);


	for(i=0,j=0,k=0;k<coun;k++,j+=4,i+=3)
	{

        if(base64_code_num[j+2]==64)
            {
                source[i]=(((base64_code_num[j]&0x3f)<<2)|((base64_code_num[j+1]>>4)&0x3));
            }

        else if(base64_code_num[j+3]==64)
            {
                source[i]=(((base64_code_num[j]&0x3f)<<2)|((base64_code_num[j+1]>>4)&0x3));
                source[i+1]=(((base64_code_num[j+1]&0xf)<<4)|((base64_code_num[j+2]>>2)&0xf));
            }


		else
		{
			source[i]=(((base64_code_num[j]&0x3f)<<2)|((base64_code_num[j+1]>>4)&0x3));
			source[i+1]=(((base64_code_num[j+1]&0xf)<<4)|((base64_code_num[j+2]>>2)&0xf));
			source[i+2]=(((base64_code_num[j+2]&0x3)<<6)|(base64_code_num[j+3]&0x3f));
		}
	}
	//free(base64_code_num);
	return source;
}

int main()
{
	while(1)
	{

            unsigned int num[MAX*2];
            int j;

            unsigned char source[MAX];
            scanf("%s",source);
            unsigned char *base64_code=base64_encode((const char *)source,strlen((const char *)source));
            printf("%s\n",base64_code);

            for(int i=0;i<strlen(base64_code);i++)
            {
                for(j=0;j<65;j++)
                {
                    if(base64_code[i]==base64words[j])
                    {
                        num[i]=j;
                        break;
                    }
                }

            }
            unsigned char *source1=base64_decode(num,strlen(base64_code));
            //此处strlen不能适用于num数组,wtf
            printf("%s\n",source1);
            free(source1);
            free(num);


	}

	return 0;
}

想要看懂,可能需要上文提到的需要解决的问题的答案。(好绕是吗,那我就把上文提到的问题复制下来)需要整理的问题:The most important question:base64编码解码的原理(这个必须弄明白,因为下面四个问题是解决编码解码的工具知识,是工具而已,并不是实现目的)其他问题:
1. 位操作(unsigned,int)
2. malloc的使用
3. C函数
4. const限定符,指针

我们一个个来:首先从最重要的问题说起,base64编码解码的过程:先放个小程序

#include <stdio.h>
#include <stdlib.h>

int main()
{
    printf("The size of char is %d",sizeof(char));
    return 0;
}

这是预先你应该知道的char占位数,一个字符占一个字节的位置,一字节即1byte=8bit,bit就是二进制位数,也就是一个char类型的字符占8个bit位,计算机存储数据的时候便是将char类型字符以8bit位数保存,即ASCII编码方式。上面程序编译后,会出现:

The size of char is 1
那么base64是什么呢,简单来说呢, 它是用64个可打印字符表示二进制所有数据方法。具体适用场景不再赘述,这里只谈原理。
先放张图看一下base64编码过程是什么样子的:

在这里插入图片描述

转换的时候,将三个byte的数据(也可以不够三个,上图就是两个字符),先后放入一个24bit的缓冲区中,先来的byte占高位。数据不足3byte的话,于缓冲区中剩下的bit用0补足。然后,每次取出6个bit,按照其值选择(最后的结果就是3、2、1个字符对应4个base64编码字符)
ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/中的字符作为编码后的输出。不断进行,直到全部输入数据转换完成。
如果最后剩下两个输入数据,在编码结果后加1个“=”;如果最后剩下一个输入数据,编码结果后加2个“=”;如果没有剩下任何数据,就什么都不要加。base64编码过程就这么简单。(网络摘抄 https://www.cnblogs.com/chengmo/p/3735917.html)
上面的编码过程中位数不够三位,所以第三个base64码‘I’的最后两位就需要补2位0,然后利用索引找到相应的字符‘I’。索引(编号)和base64的对应关系如下:
在这里插入图片描述

编码过程明白了,解码过程也比较容易了。首先将编码后的字符以4个为一组进行解码,解码过程就是编码过程的逆,最后一组解码的时候如果有=,则证明源字符不是3的倍数,所以分为有一个‘=’则缺一个字符,就是最后一组位两个字符,两个‘=’则缺两个字符,也就是说最后一组只有一个字符。进行讨论解决就ok。
那么以上就是base64编码解码的过程,即讲明原理,以下将是我们在编程过程中会用到的知识。
1.位操作(unsigned,int)

2.malloc的使用

3.C函数

4.const限定符,指针

1:位操作
既然要取8位二进制编码中的6位,就需要用到位操作,来获取并保存6位二进制数据。

那么既然有现成的博客,我就挂一篇一位大佬写的 https://blog.csdn.net/21aspnet/article/details/160037;
在这里着重区别有无符号类型的左右移动出现的问题。
2:malloc的使用
malloc()函数接受一个参数:所需要的内存字节数。malloc函数会找到合适的空闲内存块,这样的内存是匿名的。也就是说,malloc()分配内存,但是不会为其赋名。然而,它确实返回动态分配内存块的首字节地址。因此,可以把该地址赋给一个指针变量,并使用指针访问这块内存。从ANSI C标准开始,C使用一个新的malloc()函数返回值类型:指向void的指针。该类型相当于一个“通用指针”。malloc()函数可用于返回指向数组的指针、指向结构的指针等,所以通常该函数的返回值会被强制转化成匹配的类型。在ANSI C中,应该坚持使用强制类型转换,提高代码的可读性。下面是一个实例:

double * ptr;
ptr=(double ) malloc(30sizeof(double));
以上代码为30个double类型的值请求内存空间,并设置ptr指向该位置。注意,ptr被声明为指向一个double类型,而不是指向内含30个double类型值的块。回忆一下数组的知识,数组名是该数组首元素的地址。因此,如果让ptr指向这个块的首元素,便可像使用数组名一样使用它。所以可使用数组名表示指针,也可以用指针来表示数组。
malloc()使用完毕了,那么与其配套的free()就得用上。free()函数的参数是之前malloc()返回的地址,该函数释放之前malloc()分配的内存。因此动态分配内存的存储期从调用malloc()分配内存到调用free()释放内存为止。设想malloc()和free()管理着一个内存池,每次调用malloc()分配内存给程序使用,每次调用free()把内存归还内存池中,这样便可重复使用这些内存。free()的参数应该是一个指针,指向由malloc()分配的一块内存。不能用free()释放通过其他方式(如,声明一个数组)分配的内存。malloc()和free()的原型都在stdlib.h头文件中。
3:C函数
等我啥时候有心情再来吧,因为涉及的指针问题解决不了,所以我就没用第一个代码里的int find_pos(char c)函数。因为我想把所有的大型计算过程(for循环)放到函数中,这样main()函数可能会清爽一点,但是效果很明显,我记得当时老是出现输入两三个字符就崩溃的情况。以后有机会和时间再来讨论这个问题吧,因为现在我只想好好做做ctf中的逆向。第九章和第十章的Cprimerplus还是得时常更新阅读。:)果然第一遍读完啥印象也没了。

4:const限定符,指针
复习后再正儿八经整理,C primer plus
最后
kgnb!?

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值