為什么要進(jìn)行Base64編碼
Base64
最早就是用于郵件傳輸協(xié)議中的,原因是郵件傳輸協(xié)議只支持ASCII
字符傳遞涕癣,如果要傳輸二進(jìn)制文件辙培,如:圖片、視頻是無(wú)法實(shí)現(xiàn)的啥箭。因此采用Base64
將二進(jìn)制文件內(nèi)容編碼為只包含ASCII
字符的內(nèi)容谍珊。
另外在計(jì)算機(jī)中任何數(shù)據(jù)都是按ASCII
碼存儲(chǔ)的治宣,而ASCII
碼的128~255之間的值是不可見(jiàn)字符。而在網(wǎng)絡(luò)上交換數(shù)據(jù)時(shí)砌滞,比如說(shuō)從A地
傳到B地
侮邀,往往要經(jīng)過(guò)多個(gè)路由設(shè)備,由于不同的設(shè)備對(duì)字符的處理方式有一些不同贝润,這樣那些不可見(jiàn)字符就有可能被處理錯(cuò)誤绊茧,這是不利于傳輸?shù)摹K跃托枰劝褦?shù)據(jù)做一個(gè)Base64
編碼打掘,統(tǒng)統(tǒng)變成可見(jiàn)字符华畏,這樣出錯(cuò)的可能性就大降低了。
簡(jiǎn)而言之就是:Base64
主要用于將不可打印的字符轉(zhuǎn)換成可打印字符尊蚁,或者說(shuō)將二進(jìn)制數(shù)據(jù)編碼成ASCII字符亡笑。
Base64編解碼原理
Base64
是一種基于64個(gè)可打印字符來(lái)表示二進(jìn)制數(shù)據(jù),所以每6個(gè)比特為一個(gè)單元横朋,對(duì)應(yīng)某個(gè)可打印字符仑乌。3個(gè)字節(jié)有24個(gè)比特,對(duì)應(yīng)于4個(gè)Base64
單元琴锭,即3個(gè)字節(jié)可由4個(gè)可打印字符來(lái)表示晰甚。在Base64
中的可打印字符包括字母A-Z
、a-z
决帖、數(shù)字0-9
厕九,這樣共有62個(gè)字符,此外兩個(gè)可打印符號(hào)在不同的系統(tǒng)中而不同地回。一些如uuencode
的其他編碼方法扁远,和之后BinHex
的版本使用不同的64字符集來(lái)代表6個(gè)二進(jìn)制數(shù)字,但是不被稱(chēng)為Base64落君。解碼過(guò)程就是它的逆過(guò)程穿香。
Base64
編碼之所以稱(chēng)為Base64
,是因?yàn)槠涫褂?code>64個(gè)字符來(lái)對(duì)任意數(shù)據(jù)進(jìn)行編碼绎速,同理有Base32
皮获、Base16
編碼。標(biāo)準(zhǔn)Base64
編碼使用的64個(gè)字符為:
這64個(gè)字符是各種字符編碼(比如ASCI
I編碼)所使用字符的子集纹冤,并且可打印洒宝。
唯一有點(diǎn)特殊的是最后兩個(gè)字符购公,因?qū)ψ詈髢蓚€(gè)字符的選擇不同,Base64編碼又有很多變種雁歌,比如Base64 URL編碼宏浩。
Base64編碼本質(zhì)上是一種將二進(jìn)制數(shù)據(jù)轉(zhuǎn)成文本數(shù)據(jù)的方案。對(duì)于非二進(jìn)制數(shù)據(jù)靠瞎,是先將其轉(zhuǎn)換成二進(jìn)制形式比庄,然后每連續(xù)6比特(2的6次方=64)計(jì)算其十進(jìn)制值,根據(jù)該值在上面的索引表中找到對(duì)應(yīng)的字符乏盐,最終得到一個(gè)文本字符串佳窑。
具體例子分析
假設(shè)我們要對(duì)Hello!
進(jìn)行Base64
編碼,按照ASCII
表父能,其轉(zhuǎn)換過(guò)程如下圖所示:
可知Hello!
的Base64
編碼結(jié)果為SGVsbG8h
神凑,原始字符串長(zhǎng)度為6個(gè)字符,編碼后長(zhǎng)度為8個(gè)字符何吝,每3個(gè)原始字符經(jīng)Base64編碼成4個(gè)字符溉委,編碼前后長(zhǎng)度比4/3
需要注意:Base64
編碼是每3個(gè)原始字符編碼成4個(gè)字符,如果原始字符串長(zhǎng)度不能被3整除爱榕,那怎么辦瓣喊?使用0值來(lái)補(bǔ)充原始字符串。
以Hello!!
為例呆细,其轉(zhuǎn)換過(guò)程為:
注:圖表中藍(lán)色背景的二進(jìn)制0值是額外補(bǔ)充的型宝。
hello!!
字符串經(jīng)過(guò)Base64
編碼的結(jié)果為SGVsbG8hIQAA
。這里有可能會(huì)存在困惑絮爷,同樣是嘆號(hào)(!)
趴酣,前后兩個(gè)怎么不一樣啊坑夯?那是因?yàn)槲覀冃枰獫M足每3個(gè)原始字符編碼成4個(gè)字符岖寞,當(dāng)不足時(shí)添加0,所以最后嘆號(hào)(!)
變成了IQAA
柜蜈。
需要注意:最后2個(gè)零值只是為了Base64
編碼而補(bǔ)充的仗谆,所以Base64
編碼結(jié)果中的最后兩個(gè)字符AA
實(shí)際不帶有效信息,所以需要特殊處理淑履,以免解碼錯(cuò)誤隶垮。標(biāo)準(zhǔn)Base64
編碼通常用 =
字符來(lái)替換最后的A
,即最終的編碼結(jié)果為SGVsbG8hIQ==
秘噪。因?yàn)?code>= 字符并不在Base64
編碼索引表中狸吞,其意義在于結(jié)束符號(hào),在Base64
解碼時(shí)遇到=
時(shí)即可知道一個(gè)Base64
編碼字符串結(jié)束。
如果Base64
編碼字符串不會(huì)相互拼接再傳輸蹋偏,那么最后的 =
也可以省略便斥,解碼時(shí)如果發(fā)現(xiàn)Base64
編碼字符串長(zhǎng)度不能被4整除
,則先補(bǔ)充 = 字符
威始,再解碼即可枢纠。
前面也說(shuō)了解碼是對(duì)編碼的逆向操作
但注意一點(diǎn):對(duì)于最后的兩個(gè)=字符,轉(zhuǎn)換成兩個(gè) A 字符黎棠,再轉(zhuǎn)成對(duì)應(yīng)的兩個(gè)6比特二進(jìn)制0值晋渺,接著轉(zhuǎn)成原始字符之前,需要將最后的兩個(gè)6比特二進(jìn)制0值丟棄葫掉,因?yàn)樗鼈儗?shí)際上不攜帶有效信息些举。
理解Base64或其他類(lèi)似編碼的關(guān)鍵有兩點(diǎn)
- 計(jì)算機(jī)最終存儲(chǔ)和執(zhí)行的是01二進(jìn)制序列跟狱,這個(gè)二進(jìn)制序列的含義則由解碼程序/解釋程序決定
- 很多場(chǎng)景下的數(shù)據(jù)傳輸要求數(shù)據(jù)只能由簡(jiǎn)單通用的字符組成俭厚,比如:
HTTP
協(xié)議要求請(qǐng)求的首行和請(qǐng)求頭都必須是ASCII
編碼
使用場(chǎng)景
- X.509公鑰證書(shū)
對(duì)證書(shū)來(lái)說(shuō),特別是根證書(shū)驶臊,一般都是作Base64
編碼的挪挤,因?yàn)樗诰W(wǎng)上被許多人下載。電子郵件的附件一般也作Base64
編碼的关翎,因?yàn)橐粋€(gè)附件數(shù)據(jù)往往是有不可見(jiàn)字符的扛门。
- 文本傳輸
一個(gè)XML當(dāng)中包含另一個(gè)XML數(shù)據(jù),此時(shí)如果將XML數(shù)據(jù)直接寫(xiě)入顯然不合適纵寝,將XML
進(jìn)行適當(dāng)編碼存入較為方便论寨,事實(shí)上XML當(dāng)中的字符一般都是可見(jiàn)字符(0-127之間),但是由于中文的存在爽茴,可能存在不可見(jiàn)字符葬凳,直接將字符打印在外層X(jué)ML的數(shù)據(jù)中顯然不合理,那么怎么辦呢室奏?可以使用Base64
進(jìn)行編碼火焰,然后存入XML
,解碼反之
- HTTP協(xié)議
HTTP
協(xié)議當(dāng)中的key value
字段胧沫,必須進(jìn)行URLEncode
不然出現(xiàn)的等號(hào)可能使解析失敗昌简,空格也會(huì)使HTTP
請(qǐng)求解析出現(xiàn)問(wèn)題,比如:請(qǐng)求行就是以空格來(lái)劃分的POST /guowuxin/hehe HTTP/1.1
- 電子郵件(SMTP協(xié)議)
有些文本協(xié)議不支持不可見(jiàn)字符的傳遞绒怨,只能用大于32的可見(jiàn)字符來(lái)傳遞信息(協(xié)議規(guī)定)纯赎, 這個(gè)可參考阮一峰的《MIME筆記》
- 圖片base64編碼
前端在實(shí)現(xiàn)頁(yè)面時(shí),對(duì)于一些簡(jiǎn)單圖片南蹂,通常會(huì)選擇將圖片內(nèi)容直接內(nèi)嵌在頁(yè)面中犬金,避免不必要的外部資源加載,增大頁(yè)面加載時(shí)間,但是圖片數(shù)據(jù)是二進(jìn)制數(shù)據(jù)佑附,該怎么嵌入呢樊诺?絕大多數(shù)現(xiàn)代瀏覽器都支持一種名為 Data URLs
的特性,允許使用Base64
對(duì)圖片或其他文件的二進(jìn)制數(shù)據(jù)進(jìn)行編碼音同,將其作為文本字符串嵌入網(wǎng)頁(yè)中词爬。
iOS中的Base64編解碼
- 1、Base64編解碼 - OC
通過(guò)NSString+Base64
分類(lèi)來(lái)實(shí)現(xiàn)
#import <Foundation/Foundation.h>
@interface NSString (Base64)
/**
* 轉(zhuǎn)換為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
簡(jiǎn)單使用
#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
運(yùn)行結(jié)果
原文 - Hello!
Base64編碼 - SGVsbG8h
Base64解碼 - Hello!
- Base64編解碼 - Swift
擴(kuò)展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
}
}
簡(jiǎn)單使用
let string = "Hello!"
let base64String = string.base64Encoded()
print("base64String: \(base64String!)")
if let decodeString = base64String?.base64Decoded() {
print("decodeString: \(decodeString)")
}
運(yùn)行結(jié)果
base64String: SGVsbG8h
decodeString: Hello!
- 2顿膨、將圖片進(jìn)行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
// 數(shù)據(jù)大小
NSUInteger dataLength = [data length];
NSUInteger encodedBlocks = dataLength / 3;
if( (encodedBlocks + 1) >= (NSUIntegerMax / 4) ) return nil; // NSUInteger overflow check
// 需要拼接的字符數(shù)叽赊,如:數(shù)據(jù)長(zhǎng)度取余3為0恋沃,那么表示可以正處,不需要添加額外字符
NSUInteger padding = paddingTable[dataLength % 3];
// 如果需要添加字符存在必指,增加需要編碼的數(shù)量
if( padding > 0 ) encodedBlocks++;
// 總的編碼長(zhǎng)度囊咏,因?yàn)楦鶕?jù)編碼可知,編碼前的3字符變?yōu)榫幋a后的4字符
NSUInteger encodedLength = encodedBlocks * 4;
// 分配內(nèi)存大小
encodingBytes = malloc(encodedLength);
// 指針不為空
if( encodingBytes != NULL ) {
// 未編碼之前的字節(jié)數(shù)
NSUInteger rawBytesToProcess = dataLength;
// 未編碼之前的位置
NSUInteger rawBaseIndex = 0;
// 編碼的位置
NSUInteger encodingBaseIndex = 0;
// 獲取字節(jié)指針
unsigned char *rawBytes = (unsigned char *)[data bytes];
// 編碼前的字節(jié)
unsigned char rawByte1, rawByte2, rawByte3;
// 循環(huán)遍歷所有字符
while( rawBytesToProcess >= 3 ) {
// 獲取3個(gè)字符
rawByte1 = rawBytes[rawBaseIndex];
rawByte2 = rawBytes[rawBaseIndex+1];
rawByte3 = rawBytes[rawBaseIndex+2];
// 設(shè)置4個(gè)字符
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)];
// 修改位置塔橡,獲取下一個(gè)三個(gè)字符
rawBaseIndex += 3;
// 設(shè)置接下來(lái)的4個(gè)字符
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 ) {
// 添加=補(bǔ)齊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;
}
優(yōu)秀的加密庫(kù)
最后注意:Base64
并不是安全領(lǐng)域的加密算法梅割,其實(shí)Base64
只能算是一個(gè)編碼算法,對(duì)數(shù)據(jù)內(nèi)容進(jìn)行編碼來(lái)適合傳輸
參考
iOS開(kāi)發(fā)探索-Base64編碼
為什么要使用base64編碼葛家,有哪些情景需求户辞?
Base64