OpenSSL EVP之——base64编/解码

时间:2023-01-21 19:09:14

1 base64 编解码

  EVP 提供了base64编码和解码的高级接口。 Base 64编码将二进制数据转换为使用字符 A-Z,a-z,0-9,“+”和“/”表示来数据的可打印形式。每3个字节的二进制数据,编码为上诉4个字符表示的4字节数据。如果输入数据长度不是3的倍数,则输出数据将使用“=”字符在最后填充。

2 base64编码原理

  首先将每三个字节原始2进制数据在一起展开;
  然后6bit分为一个小组。每个小组前面补两个0,成为一个字节。
  把新编码的每个字节转为十进制,根据base64标准转换表,找到对应的字符。
  如果多了一个字节,则剩余两个字节用“=”填充,如果多了两个字节,则剩余一个字节用“=”填充。

3 基本数据结构(evp.h)

typedef struct evp_Encode_Ctx_st
{
/* number saved in a partial encode/decode */
int num;

/*
* The length is either the output line length (in input bytes) or the
* shortest input line length that is ok. Once decoding begins, the
* length is adjusted up each time a longer line is decoded
*/

int length;
unsigned char enc_data[80]; //待编码的数据

int line_num; /* number read on current line */
int expect_nl;
} EVP_ENCODE_CTX;

4 相关函数(evp.h encode.c)

4.1 核心函数

4.1.1 编码

#include <openssl/evp.h>

void EVP_EncodeInit(EVP_ENCODE_CTX *ctx);
void EVP_EncodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl);
void EVP_EncodeFinal(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl);

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

【EVP_EncodeInit】
  初始化ctx以启动新的编码操作。
【EVP_EncodeUpdate】
  编码in指向的缓冲区中的inl字节数据。输出存储在缓冲区out中,输出的字节数存储在outl中。调用者必须确保out指向的缓冲区足够大以容纳输出数据。只有完整的数据块(48字节)可以被直接编码完后并通过函数输出。任何剩余的字节都保存在ctx对象中,并通过后续调用EVP_EncodeUpdate()或EVP_EncodeFinal()来处理。要计算所需的输出缓冲区大小,将inl的值与ctx中保留的未处理数据量相加,并将结果除以48(忽略任何余数)。这将给出将要处理的数据块数。确保输出缓冲区包含每个块的65个字节的存储空间,因为还有一个‘\0’终结符的附加字节。可以重复调用EVP_EncodeUpdate()来处理大量的输入数据。发生错误EVP_EncodeUpdate()将outl设置为0。
【EVP_EncodeFinal】
  必须在编码操作结束时调用EVP_EncodeFinal()。它将处理ctx对象中剩余的任何部分数据块。输出数据将被存储在out,输出的数据长度将存储在* outl中。调用者者有责任确保输出缓冲区足够大以容纳不超过65字节,因为有额外的‘\0’终结器(即总共66个字节)的输出数据。
【EVP_EncodeBlock】
  EVP_EncodeBlock()对f中的输入数据进行编码,并将其存储在t中。对于每3字节的输入,将产生4字节的输出数据。如果n不能被3整除,则块被当做最后的数据块来编码,并且被填充,使得它总是可被除以4。另外还将添加‘\0’终结符字符。例如,如果提供16字节的输入数据,则创建24字节的编码数据,加上NUL终结器的1个字节(即总共25个字节)。从函数返回没有长度不包括’\0’。

4.1.2 解码

#include <openssl/evp.h>

void EVP_DecodeInit(EVP_ENCODE_CTX *ctx);
int EVP_DecodeUpdate(EVP_ENCODE_CTX *ctx, unsigned char *out, int *outl,
const unsigned char *in, int inl);
int EVP_DecodeFinal(EVP_ENCODE_CTX *ctx, unsigned
char *out, int *outl);

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

【EVP_DecodeInit】
  初始化ctx以开始新的解码操作。
【EVP_DecodeUpdate】
  解码in指向的缓冲区中inl字节的数据。输出存储在缓冲区中out,输出的字节数存储在* outl中。调用者有责任确保out指向的缓冲区足够大以容纳输出数据。该功能将尝试在4字节块中尽可能多地解码数据。任何空格,换行符或回车符都将被忽略。任何保留在结尾的未处理数据(1,2或3个字节)的部分块将保留在ctx对象中,并由后续调用EVP_DecodeUpdate()处理。如果遇到非法的base64字符,或者如果在数据中间遇到base64填充字符“=”,则函数返回-1表示错误。返回值为0或1表示数据成功处理。返回值0表示处理的最后输入数据字符包括base64填充字符“=”,因此预期不会再处理非填充字符数据。对于处理的每4个有效的64位字节(忽略空格,回车符和换行符),将产生3字节的二进制输出数据或更少(在使用填充字符“=”的数据结尾处)。
【EVP_DecodeFinal】
  必须在解码操作结束时调用EVP_DecodeFinal()。如果仍然存在任何未处理的数据,那么输入数据不能是4的倍数,因此发生错误。在这种情况下,函数返回-1。否则,该函数成功返回1。
【 EVP_DecodeBlock】
   EVP_DecodeBlock()将解码f中包含的基本64个数据的n个字节的块,并将结果存储在t中。任何前导空格将被修剪,如任何尾随的空格,换行符,回车符或EOF字符。在这样的修剪之后,f中的数据长度必须除以4.对于每4个输入字节,将产生3个输出字节。如果需要,输出将被填充0位,以确保每4个输入字节的输出始终为3个字节。此功能将返回解码的数据长度,或返回-1。

EVP_ENCODE_CTX *EVP_ENCODE_CTX_new(void);
void EVP_ENCODE_CTX_free(EVP_ENCODE_CTX *ctx);
int EVP_ENCODE_CTX_copy(EVP_ENCODE_CTX *dctx, EVP_ENCODE_CTX *sctx)
int EVP_ENCODE_CTX_num(EVP_ENCODE_CTX *ctx);

EVP_ENCODE_CTX_new()分配,初始化并返回要用于encode / decode函数的上下文。

EVP_ENCODE_CTX_free()
清理编码/解码上下文ctx并释放分配给它的空间。二进制64位数据的编码是以48个输入字节(最后一个块为少)的块执行的。对于每个48字节的输入块,编码64字节的基本64个数据被输出加上一个附加的换行符(即总共65个字节)。最后一个块(可能小于48个字节)将为每3个字节的输入输出4个字节。如果数据长度不能被3整除,那么对于最后的1或2字节的输入,仍然输出一个完整的4个字节。同样也会输出换行符。

EVP_ENCODE_CTX_num()将返回在ctx对象中待处理的尚未编码或解码的尚未处理的字节数。