文章详情

  • 游戏榜单
  • 软件榜单
关闭导航
热搜榜
热门下载
热门标签
php爱好者> php文档>oepnssl base64 编、解码

oepnssl base64 编、解码

时间:2010-08-09  来源:wavemoon

   最近项目上需要对一段二进制串进行 base64编码、解码, base64编码、解码的算法并不复杂,自行实现也不会引入太大代价,不过既然openssl 有比较成熟的实现,直接调用库代码自然是比较明智的选择。    具体实践过程中,遭遇些细节问题,折腾许久,方才搞定,特此记下,便于以后查阅,同时shared给后来人:    openssl 中 base64编码相关的几个函数如下:    ----------------- 转载自其它blog,正确性已经过验证 ------------------------------    编码函数:  

Ø         EVP_EncodeInit函数

原型:void EVP_EncodeInit(EVP_ENCODE_CTX *ctx);

功能:该函数初始化一个用来进行base64编码的结构,事实上,该函数只是简单设置了结构里面几个常量的长度。

参数:

ctx:base64设备上下文。

Ø         EVP_EncodeUpdate函数

原型:void EVP_EncodeUpdate(EVP_ENCODE_CTX *ctx,unsigned char *out,int *outl,unsigned char *in,int inl);

功能:该函数将参数in里面的inl自己数据拷贝到结构体ctx里面,如果结构体里面有数据,就同时将结构体里面的数据进行BASE64编码并输出到参数out指向的缓存里面,输出数据的长度保存在outl里面。注意,在第一次调用本函数的时候,虽然往结构体里面拷贝数据了,但是结构体ctx里面开始是没有输入数据存在并且输入数据长度不超出ctx内部存储数据的最长限制,就不会有任何数据被进行BASE64编码,也就是说,不会有任何数据输出;但是如果输入数据长度比内部存储的数据长,那么就会输出部分经过BASE64编码的数据。数据输出总是在下一层输入前完成的。

参数:

ctx:base64设备上下文。

out:存放编码后的数据缓冲区。

outl:编码后的数据长度。

in:编码前的数据。

inl:编码前的数据长度。

Ø         EVP_EncodeFinal函数

原型:void EVP_EncodeFinal(EVP_ENCODE_CTX *ctx,unsigned char *out,int *outl);

功能:该函数将结构体ctx里面剩余数据进行BASE64编码并写入到参数out里面去,输出数据的长度保存在outl里面。

参数:

ctx:base64设备上下文。

out:存放编码后的数据。

outl:编码后的数据长度。

Ø         EVP_EncodeBlock函数

  原型:int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n);

  功能:该函数将参数f里面的字符串里面的n个字节的字符串进行BASE64编码并输出到参数t里面。返回数据的字节长度。事实上,在函数EVP_EncodeUpdate和EVP_EncodeFinal里面就调用了该函数完成BASE64编码功能。

参数:

t:接收编码后的数据缓冲区。

f:编码前的数据。

n:编码前的数据长度。

 

   解码函数:           Ø       EVP_DecodeInit函数

原型:void EVP_DecodeInit(EVP_ENCODE_CTX *ctx);

功能:该函数初始化一个用来进行BASE64解码的数据结构。

参数:

ctx:base64设备上下文。

Ø         EVP_DecodeUpdate函数

原型:int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx,unsigned char *out,int *outl,unsigned char *in, int inl);

功能:该函数将参数in里面inl字节的数据拷贝到结构体ctx里面。如果结构体里面已经有数据,那么这些数据就会先进行BASE64解码,然后输出到参数out指向的内存中,输出的字节数保存在参数outl里面。输入数据为满行的数据时,返回为1;如果输入数据是最后一行数据的时候,返回0;返回-1则表明出错了。

参数:

ctx:base64设备上下文。

out:存放解码后的数据缓冲区。

outl:解码后的数据长度。

in:解码前的数据。

inl:解码前的数据长度。

Ø         EVP_DecodeFinal函数

原型:int EVP_DecodeFinal(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl);

功能:该函数将结构体ctx里面剩余的数据进行BASE64解码并输出到参数out指向的内存中,输出数据长度为outl字节。成功返回1,否则返回-1。

参数:

ctx:base64设备上下文。

out:解码后的数据。

outl:解码后的数据长度。

Ø         EVP_DecodeBlock函数

原型:int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n);

功能:该函数将字符串f中的n字节数据进行BASE64解码,并输出到t指向的内存中,输出数据长度为outl。成功返回解码的数据长度,返回返回-1。

参数:

t:接收解码后的数据缓冲区。

f:解码前的数据。

       n:解码前的数据长度。    --------------------- 上述函数声明为转载内容 ---------------------------    这些函数声明对应声明在头文件 /usr/include/openssl/evp.h 中。(具体路径视安装情况可能略有差异)。      按照base64的算法,任何长度的串编码后长度均未4的倍数, 解码后均未3的倍数。    理论上,编码(encode)时,如果输入串不是3的倍数,会在后面补0,以保持3的倍数,反映到encode后的串,就是后面对应补了'=', '='在正常base64编码中不会存在,因此,base64解码时有能力去除尾部的'\0'(虽然上述有些函数没有这么干)。          问题1: 直接使用EVP_EncodeBlock(...) / EVP_DecodeBlock(...) 编码、解码,原串经过编码、解码后可能无法还原!---尾部可能会多'\0', 比如:           '1234'           --> EVP_EncodeBlock(...) 变为:'MTIzNA=='             --> EVP_DecodeBlock(...) 变为:'1234\0\0' 尾部多了两个\0           当然这对于以\0结尾的字符串是没影响的,对于二进制数据则直接意味着错误!       为简便起见,我在程序中直接使用了EVP_EncodeBlock /EVP_DecodeBlock 编、解码, 这个问题困扰了很长时间,更无语的是,google、baidu 关于openssl base64编、解码的说明寥寥无几,仅有的相关博文也几乎仅限于api的说明,还被copy来copy去,很多函数原型都不对了!  相关函数的声明头文件中更是一行注释都没有(这个比较ft -,-|| )      最后在google下 用纯e文搜索才找出点端倪:(一下内容未查阅源码,为e文论坛上直接摘录、翻译)       EVP_DecodeBlock内部同样调用EVP_DecodeInit + EVP_DecodeUpdate + Evp_DecodeFinal实现,但是并未处理尾部的'='字符,因此结果字符串长度总是为3的倍数。若要获取精确的正确长度,外部需添加额外代码,类似下面这样:         while(input_str[--input_str_len] = '=') output_len--;         return output_len; // 获取实际长度         实际就是原输入串尾部有几个 '=', decode后输出串的长度减几久ok了。    问题2: 直接使用EVP_DecodeInit + EVP_DecodeUpdate + EVP_DecodeFinal 组合进行decode是可以处理尾部'='字符,正确获取原串长度的,不过code会看起来零碎很多,用起来感觉也不是很友好,贴个解码的示例code 如下,编码类同。(ms现在都baidu不到示例code的)     <code>     //!< encode test     int src_str_size = 60;
    unsigned char *src_str = (unsigned char *)malloc(src_str_size);
    memset(src_str, '1', src_str_size);
    unsigned char *encode_str = (unsigned char *)malloc( (src_str_size - 1) / 3 * 4 + 4);
    int encode_str_size = EVP_EncodeBlock(encode_str, src_str, src_str_size);
    printf("encode_str_size=%d encode_str:'%s'\n", encode_str_size, encode_str);
    //!< decode test     EVP_ENCODE_CTX ctx;
    EVP_DecodeInit(&ctx);
    unsigned char *decode_str = (unsigned char *)malloc(src_str_size);
    int decode_str_size = 0;
    int len = 20;   //!< 每次decode 的字节数 
    int decode_len = 0;
    int offset = 0;
    while(1)
    {  
        if(offset + len > encode_str_size)
        {  
            len = encode_str_size - offset;
        }  
   
        int ret = EVP_DecodeUpdate(&ctx, decode_str + decode_str_size, &decode_len,
                                      encode_str + offset, len);
        if(ret == 0) break;
        if(ret == -1)
        {
            printf("error...\n");
            break;
        }
        offset += len;
        decode_str_size += decode_len;
    }
    EVP_DecodeFinal(&ctx, decode_str, &decode_len);
    decode_str_size += decode_len;
    printf("decode_str:'%s' decode_str_len=%d\n",
            decode_str, decode_str_size);
    </code>        其中EVP_DecodeUpdate 函数,尾部的len参数不可太长(最大80,超过会报错),     ----------------   openssl 库应用甚少,了解也是皮毛,欢迎大家指正。   ps:江南计算所 赵春平的 《openssl 编程》说明是我目前找到的除源码外比较详见的说明了。       baidu 文库有相关文档 ---------------- 补充几个注意点: 
1, 使用EVP_EncodeInit + EVP_EncodeUpdate + EVP_EncodeFinal 进行编码,输出为格式化的数据,每个80个字符插入一个回车。
   同样使用EVP_DecodeInit + EVP_DecodeUpdate + EVP_DecodeUpdate 进行解码,输入只接受格式化的数据,即每隔80个字符一个回车,否则超过80个字符,则解码失败。
2,要避免encode输出、decode输入为格式化后的数据,可以直接使用EVP_EncodeBlock编码,使用EVP_DecodeBlock解码,不过EVP_DecodeBlock解码后的数据长度恒为3的倍数,原串不足3的倍数的,会酌情补'\0'。要严格还原原串,需根据EVP_DecodeBlock输入串尾部'='个数减去解码后数据尾部的'\0' 3, 鉴于以上2个原因,编、解码使用的方式要匹配,如使用EVP_EncodeUpdate编码,则对应使用EVP_DecodeUpdate解码,否则,串长超过80,可能存在潜在解码错误。                  
相关阅读 更多 +
排行榜 更多 +
幸运硬币官方正版下载

幸运硬币官方正版下载

休闲益智 下载
宝宝来找茬手机版 v9.86.00.00 安卓版

宝宝来找茬手机版 v9.86.00.00 安卓版

休闲益智 下载
翻滚飞机大战最新版 v1.0.4 安卓版

翻滚飞机大战最新版 v1.0.4 安卓版

飞行射击 下载