为什么要进行Base64编码
Base64
最早就是用于邮件传输协议中的,原因是邮件传输协议只支持ASCII
字符传递,如果要传输二进制文件,如:图片、视频是无法实现的。因此采用Base64
将二进制文件内容编码为只包含ASCII
字符的内容。
另外在计算机中任何数据都是按ASCII
码存储的,而ASCII
码的128~255之间的值是不可见字符。而在网络上交换数据时,比如说从A地
传到B地
,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就需要先把数据做一个Base64
编码,统统变成可见字符,这样出错的可能性就大降低了。
简而言之就是:Base64
主要用于将不可打印的字符转换成可打印字符,或者说将二进制数据编码成ASCII字符。
Base64编解码原理
Base64
是一种基于64个可打印字符来表示二进制数据,所以每6个比特为一个单元,对应某个可打印字符。3个字节有24个比特,对应于4个Base64
单元,即3个字节可由4个可打印字符来表示。在Base64
中的可打印字符包括字母A-Z
、a-z
、数字0-9
,这样共有62个字符,此外两个可打印符号在不同的系统中而不同。一些如uuencode
的其他编码方法,和之后BinHex
的版本使用不同的64字符集来代表6个二进制数字,但是不被称为Base64。解码过程就是它的逆过程。
Base64
编码之所以称为Base64
,是因为其使用64
个字符来对任意数据进行编码,同理有Base32
、Base16
编码。标准Base64
编码使用的64个字符为:
这64个字符是各种字符编码(比如ASCI
I编码)所使用字符的子集,并且可打印。
唯一有点特殊的是最后两个字符,因对最后两个字符的选择不同,Base64编码又有很多变种,比如Base64 URL编码。
Base64编码本质上是一种将二进制数据转成文本数据的方案。对于非二进制数据,是先将其转换成二进制形式,然后每连续6比特(2的6次方=64)计算其十进制值,根据该值在上面的索引表中找到对应的字符,最终得到一个文本字符串。
具体例子分析
假设我们要对Hello!
进行Base64
编码,按照ASCII
表,其转换过程如下图所示:
可知Hello!
的Base64
编码结果为SGVsbG8h
,原始字符串长度为6个字符,编码后长度为8个字符,每3个原始字符经Base64编码成4个字符,编码前后长度比4/3
需要注意:Base64
编码是每3个原始字符编码成4个字符,如果原始字符串长度不能被3整除,那怎么办?使用0值来补充原始字符串。
以Hello!!
为例,其转换过程为:
注:图表中蓝色背景的二进制0值是额外补充的。
hello!!
字符串经过Base64
编码的结果为SGVsbG8hIQAA
。这里有可能会存在困惑,同样是叹号(!)
,前后两个怎么不一样啊?那是因为我们需要满足每3个原始字符编码成4个字符,当不足时添加0,所以最后叹号(!)
变成了IQAA
。
需要注意:最后2个零值只是为了Base64
编码而补充的,所以Base64
编码结果中的最后两个字符AA
实际不带有效信息,所以需要特殊处理,以免解码错误。标准Base64
编码通常用 =
字符来替换最后的A
,即最终的编码结果为SGVsbG8hIQ==
。因为= 字符
并不在Base64
编码索引表中,其意义在于结束符号,在Base64
解码时遇到=
时即可知道一个Base64
编码字符串结束。
如果Base64
编码字符串不会相互拼接再传输,那么最后的 =
也可以省略,解码时如果发现Base64
编码字符串长度不能被4整除
,则先补充 = 字符
,再解码即可。
前面也说了解码是对编码的逆向操作
但注意一点:对于最后的两个=字符,转换成两个 A 字符,再转成对应的两个6比特二进制0值,接着转成原始字符之前,需要将最后的两个6比特二进制0值丢弃,因为它们实际上不携带有效信息。
理解Base64或其他类似编码的关键有两点
- 计算机最终存储和执行的是01二进制序列,这个二进制序列的含义则由解码程序/解释程序决定
- 很多场景下的数据传输要求数据只能由简单通用的字符组成,比如:
HTTP
协议要求请求的首行和请求头都必须是ASCII
编码
使用场景
- X.509公钥证书
对证书来说,特别是根证书,一般都是作Base64
编码的,因为它要在网上被许多人下载。电子邮件的附件一般也作Base64
编码的,因为一个附件数据往往是有不可见字符的。
- 文本传输
一个XML当中包含另一个XML数据,此时如果将XML数据直接写入显然不合适,将XML
进行适当编码存入较为方便,事实上XML当中的字符一般都是可见字符(0-127之间),但是由于中文的存在,可能存在不可见字符,直接将字符打印在外层XML的数据中显然不合理,那么怎么办呢?可以使用Base64
进行编码,然后存入XML
,解码反之
- HTTP协议
HTTP
协议当中的key value
字段,必须进行URLEncode
不然出现的等号可能使解析失败,空格也会使HTTP
请求解析出现问题,比如:请求行就是以空格来划分的POST /guowuxin/hehe HTTP/1.1
- 电子邮件(SMTP协议)
有些文本协议不支持不可见字符的传递,只能用大于32的可见字符来传递信息(协议规定), 这个可参考阮一峰的《MIME笔记》
- 图片base64编码
前端在实现页面时,对于一些简单图片,通常会选择将图片内容直接内嵌在页面中,避免不必要的外部资源加载,增大页面加载时间,但是图片数据是二进制数据,该怎么嵌入呢?绝大多数现代浏览器都支持一种名为 Data URLs
的特性,允许使用Base64
对图片或其他文件的二进制数据进行编码,将其作为文本字符串嵌入网页中。
iOS中的Base64编解码
- 1、Base64编解码 - OC
通过NSString+Base64
分类来实现
#import <Foundation/Foundation.h>
@interface NSString (Base64)
/**
* 转换为Base64编码
*/
- (NSString *)base64EncodedString;
/**
* 将Base64编码还原
*/
- (NSString *)base64DecodedString;
@end
#import "NSString+Base64.h"
@implementation NSString (Base64)
- (NSString *)base64EncodedString;
{
NSData *data = [self dataUsingEncoding: NSUTF8StringEncoding];
return [data base64EncodedStringWithOptions:0];
}
- (NSString *)base64DecodedString
{
NSData *data = [[NSData alloc]initWithBase64EncodedString:self options:0];
return [[NSString alloc]initWithData:data encoding: NSUTF8StringEncoding];
}
@end
简单使用
#import "ViewController.h"
#import "NSString+Base64.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *string = @"Hello!";
NSLog(@"原文 - %@", string);
NSString *base64String = [string base64EncodedString];
NSLog(@"Base64编码 - %@", base64String);
NSString *decodeString = [base64String base64DecodedString];
NSLog(@"Base64解码 - %@", decodeString);
}
@end
运行结果
原文 - Hello!
Base64编码 - SGVsbG8h
Base64解码 - Hello!
- Base64编解码 - Swift
扩展String
,添加便捷编解码方法
extension String {
// Base64 encoding a string
func base64Encoded() -> String? {
if let data = self.data(using: .utf8) {
return data.base64EncodedString()
}
return nil
}
// Base64 decoding a string
func base64Decoded() -> String? {
if let data = Data(base64Encoded: self) {
return String(data: data, encoding: .utf8)
}
return nil
}
}
简单使用
let string = "Hello!"
let base64String = string.base64Encoded()
print("base64String: \(base64String!)")
if let decodeString = base64String?.base64Decoded() {
print("decodeString: \(decodeString)")
}
运行结果
base64String: SGVsbG8h
decodeString: Hello!
- 2、将图片进行Base64编解码
func encodeAndDecodeUIImage() {
let image = UIImage(named: "pig")!
let imageData = image.pngData()!
let base64String = imageData.base64EncodedString(options: .lineLength64Characters)
print("base64String: \(base64String)")
if let dataDecoded = Data(base64Encoded: base64String, options: .ignoreUnknownCharacters) {
let decodedImage = UIImage(data: dataDecoded)
imageView.image = decodedImage
}
}
Convert between UIImage and Base64 string
- 3、分析Base64编码
+(NSString *)base64StringFromData:(NSData *)data
{
NSString *encoding = nil;
unsigned char *encodingBytes = NULL;
@try {
// 字符集合
static char encodingTable[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//
static NSUInteger paddingTable[] = {0,2,1};
// Table 1: The Base 64 Alphabet
//
// Value Encoding Value Encoding Value Encoding Value Encoding
// 0 A 17 R 34 i 51 z
// 1 B 18 S 35 j 52 0
// 2 C 19 T 36 k 53 1
// 3 D 20 U 37 l 54 2
// 4 E 21 V 38 m 55 3
// 5 F 22 W 39 n 56 4
// 6 G 23 X 40 o 57 5
// 7 H 24 Y 41 p 58 6
// 8 I 25 Z 42 q 59 7
// 9 J 26 a 43 r 60 8
// 10 K 27 b 44 s 61 9
// 11 L 28 c 45 t 62 +
// 12 M 29 d 46 u 63 /
// 13 N 30 e 47 v
// 14 O 31 f 48 w (pad) =
// 15 P 32 g 49 x
// 16 Q 33 h 50 y
// 数据大小
NSUInteger dataLength = [data length];
NSUInteger encodedBlocks = dataLength / 3;
if( (encodedBlocks + 1) >= (NSUIntegerMax / 4) ) return nil; // NSUInteger overflow check
// 需要拼接的字符数,如:数据长度取余3为0,那么表示可以正处,不需要添加额外字符
NSUInteger padding = paddingTable[dataLength % 3];
// 如果需要添加字符存在,增加需要编码的数量
if( padding > 0 ) encodedBlocks++;
// 总的编码长度,因为根据编码可知,编码前的3字符变为编码后的4字符
NSUInteger encodedLength = encodedBlocks * 4;
// 分配内存大小
encodingBytes = malloc(encodedLength);
// 指针不为空
if( encodingBytes != NULL ) {
// 未编码之前的字节数
NSUInteger rawBytesToProcess = dataLength;
// 未编码之前的位置
NSUInteger rawBaseIndex = 0;
// 编码的位置
NSUInteger encodingBaseIndex = 0;
// 获取字节指针
unsigned char *rawBytes = (unsigned char *)[data bytes];
// 编码前的字节
unsigned char rawByte1, rawByte2, rawByte3;
// 循环遍历所有字符
while( rawBytesToProcess >= 3 ) {
// 获取3个字符
rawByte1 = rawBytes[rawBaseIndex];
rawByte2 = rawBytes[rawBaseIndex+1];
rawByte3 = rawBytes[rawBaseIndex+2];
// 设置4个字符
encodingBytes[encodingBaseIndex] = encodingTable[((rawByte1 >> 2) & 0x3F)];
encodingBytes[encodingBaseIndex+1] = encodingTable[((rawByte1 << 4) & 0x30) | ((rawByte2 >> 4) & 0x0F) ];
encodingBytes[encodingBaseIndex+2] = encodingTable[((rawByte2 << 2) & 0x3C) | ((rawByte3 >> 6) & 0x03) ];
encodingBytes[encodingBaseIndex+3] = encodingTable[(rawByte3 & 0x3F)];
// 修改位置,获取下一个三个字符
rawBaseIndex += 3;
// 设置接下来的4个字符
encodingBaseIndex += 4;
rawBytesToProcess -= 3;
}
rawByte2 = 0;
switch (dataLength-rawBaseIndex) {
case 2:
rawByte2 = rawBytes[rawBaseIndex+1];
case 1:
rawByte1 = rawBytes[rawBaseIndex];
encodingBytes[encodingBaseIndex] = encodingTable[((rawByte1 >> 2) & 0x3F)];
encodingBytes[encodingBaseIndex+1] = encodingTable[((rawByte1 << 4) & 0x30) | ((rawByte2 >> 4) & 0x0F) ];
encodingBytes[encodingBaseIndex+2] = encodingTable[((rawByte2 << 2) & 0x3C) ];
// we can skip rawByte3 since we have a partial block it would always be 0
break;
}
// compute location from where to begin inserting padding, it may overwrite some bytes from the partial block encoding
// if their value was 0 (cases 1-2).
encodingBaseIndex = encodedLength - padding;
while( padding-- > 0 ) {
// 添加=补齐4/3
encodingBytes[encodingBaseIndex++] = '=';
}
encoding = [[NSString alloc] initWithBytes:encodingBytes length:encodedLength encoding:NSASCIIStringEncoding];
}
}
@catch (NSException *exception) {
encoding = nil;
NSLog(@"WARNING: error occured while tring to encode base 32 data: %@", exception);
}
@finally {
if( encodingBytes != NULL ) {
free( encodingBytes );
}
}
return encoding;
}
优秀的加密库
最后注意:Base64
并不是安全领域的加密算法,其实Base64
只能算是一个编码算法,对数据内容进行编码来适合传输