Base64在不同语言对接时,其实是有些小坑的,之前有碰过。
首先Base64的是啥? 阮一峰有一篇写得很赞的文章说得很明白。
然后是怎么编码?
我用OpenSSL写了个Base64解编码实现:
/*
用openssl来做Base64加解密.
Author: xcl
Date:2015-9-17
*/
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include "openssl/ssl.h"
#include <openssl/bio.h>
#include <openssl/evp.h>
#if defined(WIN32) || defined(_WIN64)
#pragma comment(lib, "libeay32.lib")
#pragma comment(lib, "ssleay32.lib")
#endif
/*
NO_PADDING : 略去加密字符串最后的”=”
NO_WRAP : 略去所有的换行符
Android中的Base64.DEFAULT会在每超过76个字符后,自动加换行符,
并且在字符串最后也会加一个换行符.
所以与其对接时(比如IOS)要注意这点.可建议其选Base64.NO_WRAP类型.
而OpenSSL的命令行进行Base64时,则每超过64个字符就自动加换行符,并且也在最末尾自动加换行.
解编码命令行如下:
echo "Hello"|openssl enc -base64
echo "SGVsbG8K"|openssl enc -base64 -d
*/
enum Base64{ NO_PADDING = 0, NO_WRAP};
std::string Decode(std::string data,const int mode) {
printf("[Decode] 解密前:%s\r\n", data.c_str());
size_t length = data.length();
if (length == 0)return "";
BIO *b64 = BIO_new(BIO_f_base64());
if (mode == Base64::NO_WRAP) {
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
}
BIO *mem = nullptr;
mem = BIO_new_mem_buf(const_cast<char *>(data.c_str()), -1);
mem = BIO_push(b64, mem);
//char inbuf[512];
//int inlen;
//while ((inlen = BIO_read(b64, inbuf, 512)) > 0) {
// printf("解密后:%s \n\r\n", inbuf);
//}
char * buffer = (char *)malloc(length);
memset(buffer, 0, length);
BIO_read(mem, buffer, length);
std::string ret(buffer);
free(buffer);
BIO_free_all(mem);
printf("[Decode] 解密后:%s\r\n", ret.c_str());
return ret;
}
std::string Encode(std::string data,const int mode) {
printf("[Encode] 加密前:%s\r\n", data.c_str());
if (data.length() == 0)return "";
//写入时,采用base64编码
BIO *b64 = BIO_new(BIO_f_base64());
// 如果打开 BIO_FLAGS_BASE64_NO_NL ,则传入时要带"\n" 即"Hello\n",
// 否则,还原后会丢失两个字符.
if (Base64::NO_WRAP == mode) {
data.append("\n");
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
}
// Create a memory BIO
BIO *mem = BIO_new(BIO_s_mem());
b64 = BIO_push(b64, mem);
BIO_write(b64, data.c_str(),data.length());
BIO_flush(b64);
//BIO_puts(b64, data.c_str());
//得到base64后的字符串
BUF_MEM *pBuf = nullptr;
BIO_get_mem_ptr(mem, &pBuf);
std::string ret(pBuf->data);
free(pBuf);
BIO_set_close(b64, BIO_NOCLOSE);
BIO_free_all(b64);
printf("[Encode] 加密后:%s\r\n", ret.c_str());
return ret;
}
void main() {
std::string s = "Hello";
printf("//\r\n");
printf("NO_PADDING:\r\n");
Base64 mode = Base64::NO_PADDING;
s = Encode(s,mode);
Decode(s,mode);
printf("//\r\n");
printf("NO_WRAP:\r\n");
s = "Hello";
mode = Base64::NO_WRAP;
s = Encode(s, mode);
Decode(s, mode);
system("pause");
}
/*
//
NO_PADDING:
[Encode] 加密前:Hello
[Encode] 加密后:SGVsbG8=
[Decode] 解密前:SGVsbG8=
[Decode] 解密后:Hello
//
NO_WRAP:
[Encode] 加密前:Hello
[Encode] 加密后:SGVsbG8K
[Decode] 解密前:SGVsbG8K
[Decode] 解密后:Hello
请按任意键继续. . .
*/
对于Golang而言,官网就提供了一个很好的例子,有标准的Base64和URL的编码我在这贴下这个例子:
/*
Go的Base64加解密.
Author: xcl
Date:2015-9-17
*/
package main
import b64 "encoding/base64"
import "fmt"
func main() {
data := "abc123!?$*&()'-=@~"
fmt.Println(data)
sEnc := b64.StdEncoding.EncodeToString([]byte(data))
fmt.Println("STD编码:", sEnc)
sDec, _ := b64.StdEncoding.DecodeString(sEnc)
fmt.Println("STD解码:", string(sDec))
uEnc := b64.URLEncoding.EncodeToString([]byte(data))
fmt.Println("URL编码:", uEnc)
uDec, _ := b64.URLEncoding.DecodeString(uEnc)
fmt.Println("URL解码:", string(uDec))
}
/*
运行结果:
a+b/c123!?$*&()'-=@~
STD编码: YStiL2MxMjMhPyQqJigpJy09QH4=
STD解码: a+b/c123!?$*&()'-=@~
URL编码: YStiL2MxMjMhPyQqJigpJy09QH4=
URL解码: a+b/c123!?$*&()'-=@~
*/
可以先去看看Go标准库中Base64的源码:
const encodeStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
const encodeURL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
URLEncoding被称为
URL安全的Base64编码。适用于以URL方式传递Base64编码结果的场景。
它的特点是,将字符串中的加号+换成中划线-,并且将斜杠/换成下划线_,同时尾部保持填充等号=。
为什么在URL时要替换标准BASE64这两个字符?
引用 维基百科的说明:
因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式,而这些“%”号在存入数据库时还需要再进行转换,因为ANSI SQL中已将“%”号用作通配符。
其实就是为了不影响入库和SQL查询,所以才这样的.
如果对Go是怎么实现的感兴趣,这有源码.
嗯,大致就这些.
BLOG: http://blog.youkuaiyun.com/xcl168