iOS中Base64編解碼的使用

為什么要進(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-Za-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è)字符為:

1485695-5311d9d624394d61.jpg

這64個(gè)字符是各種字符編碼(比如ASCII編碼)所使用字符的子集纹冤,并且可打印洒宝。

唯一有點(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ò)程如下圖所示:

1485695-e572f6b071ef49c0.jpg

可知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ò)程為:

1485695-142384406e84fd12.jpg

注:圖表中藍(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;
}

Base64

優(yōu)秀的加密庫(kù)

CryptoSwift

最后注意:Base64并不是安全領(lǐng)域的加密算法梅割,其實(shí)Base64只能算是一個(gè)編碼算法,對(duì)數(shù)據(jù)內(nèi)容進(jìn)行編碼來(lái)適合傳輸

參考

iOS開(kāi)發(fā)探索-Base64編碼
為什么要使用base64編碼葛家,有哪些情景需求户辞?
Base64

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市癞谒,隨后出現(xiàn)的幾起案子底燎,更是在濱河造成了極大的恐慌,老刑警劉巖弹砚,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件双仍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡迅栅,警方通過(guò)查閱死者的電腦和手機(jī)殊校,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)读存,“玉大人为流,你說(shuō)我怎么就攤上這事∪貌荆” “怎么了敬察?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)尔当。 經(jīng)常有香客問(wèn)我莲祸,道長(zhǎng)蹂安,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任锐帜,我火速辦了婚禮田盈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘缴阎。我一直安慰自己允瞧,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布蛮拔。 她就那樣靜靜地躺著述暂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪建炫。 梳的紋絲不亂的頭發(fā)上畦韭,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音肛跌,去河邊找鬼艺配。 笑死,一個(gè)胖子當(dāng)著我的面吹牛惋砂,可吹牛的內(nèi)容都是我干的妒挎。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼西饵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了鳞芙?” 一聲冷哼從身側(cè)響起眷柔,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎原朝,沒(méi)想到半個(gè)月后驯嘱,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喳坠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年鞠评,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壕鹉。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡剃幌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出晾浴,到底是詐尸還是另有隱情负乡,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布脊凰,位于F島的核電站抖棘,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜切省,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一最岗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧朝捆,春花似錦仑性、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至何陆,卻和暖如春晨汹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贷盲。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工淘这, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巩剖。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓铝穷,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親佳魔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子曙聂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348