2015年,咱們在青雲平臺上實現了「百度雲觀測」應用。青雲應用本質上是一個iframe,在向iframe服務方發送的請求中會攜帶一些數據,青雲平臺會使用Base64 URL
對這些數據進行編碼,其提供的編碼解碼算法示例以下:php
// php版本
function base64_URL_encode($data) { return rtrim(strtr(base64_encode($data), '+/', '-_'), '='); } function base64_URL_decode($data) { return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT)); }
能夠看出,Base64 URL
是標準Base64編碼的一個變種,分別用 -
、_
替換標準Base64編碼結果中的 +
、 /
,並刪除結果最後的 =
。css
在實現 「百度雲觀測」 青雲應用時,我在想:html
本文是圍繞這兩個問題思考和實踐的結果。前端
我認爲,理解Base64或其餘相似編碼的關鍵有兩點:python
以青雲應用爲例,簡單解釋這兩點。青雲平臺經過POST一個表單來獲取iframe,表單有 payload
和 signature
兩項, payload
本來是一個JSON對象,其中的鍵值可能包含一些特殊字符,好比 &
、/
等,因爲青雲設計的一種通用的請求交互方案,須要考慮iframe服務方服務器端的各類可能實現,有些服務器端實現沒有考慮表單值有這些特殊字符,或者POST請求被中間服務器轉換成GET請求再次發出,對於URL來講,&
、/
都是具備特殊含義的字符,因此須要對請求數據進行特殊編碼避免這些字符出現 - 數據發送方對數據按規則進行編碼,接收方對應地按規則解碼數據。git
Base64編碼之因此稱爲Base64,是由於其使用64個字符來對任意數據進行編碼,同理有Base3二、Base16編碼。標準Base64編碼使用的64個字符爲:github
這64個字符是各類字符編碼(好比ASCII編碼)所使用字符的子集,基本,而且可打印。惟一有點特殊的是最後兩個字符,因對最後兩個字符的選擇不一樣,Base64編碼又有不少變種,好比Base64 URL編碼。golang
Base64編碼本質上是一種將二進制數據轉成文本數據的方案。對於非二進制數據,是先將其轉換成二進制形式,而後每連續6比特(2的6次方=64)計算其十進制值,根據該值在上面的索引表中找到對應的字符,最終獲得一個文本字符串。算法
假設咱們要對 Hello!
進行Base64編碼,按照ASCII表,其轉換過程以下圖所示:數據庫
可知 Hello!
的Base64編碼結果爲 SGVsbG8h
,原始字符串長度爲6個字符,編碼後長度爲8個字符,每3個原始字符經Base64編碼成4個字符,編碼先後長度比4/3,這個長度比很重要 - 比原始字符串長度短,則須要使用更大的編碼字符集,這並不咱們想要的;長度比越大,則須要傳輸越多的字符,傳輸時間越長。Base64應用普遍的緣由是在字符集大小與長度比之間取得一個較好的平衡,適用於各類場景。
是否是以爲Base64編碼原理很簡單?
但這裏須要注意一個點:Base64編碼是每3個原始字符編碼成4個字符,若是原始字符串長度不能被3整除,那怎麼辦?使用0值來補充原始字符串。
以 Hello!!
爲例,其轉換過程爲:
注:圖表中藍色背景的二進制0值是額外補充的。
Hello!!
Base64編碼的結果爲 SGVsbG8hIQAA
。最後2個零值只是爲了Base64編碼而補充的,在原始字符中並無對應的字符,那麼Base64編碼結果中的最後兩個字符 AA
實際不帶有效信息,因此須要特殊處理,以避免解碼錯誤。
標準Base64編碼一般用 =
字符來替換最後的 A
,即編碼結果爲 SGVsbG8hIQ==
。由於 =
字符並不在Base64編碼索引表中,其意義在於結束符號,在Base64解碼時遇到 =
時便可知道一個Base64編碼字符串結束。
若是Base64編碼字符串不會相互拼接再傳輸,那麼最後的 =
也能夠省略,解碼時若是發現Base64編碼字符串長度不能被4整除,則先補充 =
字符,再解碼便可。
解碼是對編碼的逆向操做,但注意一點:對於最後的兩個 =
字符,轉換成兩個 A
字符,再轉成對應的兩個6比特二進制0值,接着轉成原始字符以前,須要將最後的兩個6比特二進制0值丟棄,由於它們實際上不攜帶有效信息。
爲了理解Base64編碼解碼過程,我的實現了一個很是簡陋的Base64編碼解碼程序,見:youngsterxyf/xiaBase64。
因爲Base64應用普遍,因此不少編程語言的標準庫都內置Base64編碼解碼包,如:
本文開始提到的青雲應用例子只是Base64編碼的應用場景之一。因爲Base64編碼在字符集大小與編碼後數據長度之間作了較好的平衡,以及Base64編碼變種形式的多樣,使得Base64編碼的應用場景很是普遍。下面舉2個經常使用常見的例子。
前端在實現頁面時,對於一些簡單圖片,一般會選擇將圖片內容直接內嵌在頁面中,避免沒必要要的外部資源加載,增大頁面加載時間,可是圖片數據是二進制數據,該怎麼嵌入呢?絕大多數現代瀏覽器都支持一種名爲 Data URLs
的特性,容許使用Base64對圖片或其餘文件的二進制數據進行編碼,將其做爲文本字符串嵌入網頁中。以百度搜索首頁爲例,其中語音搜索的圖標是個背景圖片,其內容以 Data URLs
形式直接寫在css中,這個css內容又直接嵌在HTML頁面中,以下圖所示:
Data URLs
格式爲:url(data:文件類型;編碼方式,編碼後的文件內容)
。
固然,也能夠直接基於image標籤嵌入圖片,以下所示:
<img alt="Embedded Image" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIA..." />
但請注意:若是圖片較大,圖片的色彩層次比較豐富,則不適合使用這種方式,由於其Base64編碼後的字符串很是大,會明顯增大HTML頁面,影響加載速度。
咱們的電子郵件系統,通常是使用SMTP(簡單郵件傳輸協議)將郵件從客戶端發往服務器端,郵件客戶端使用POP3(郵局協議,第3版本)或IMAP(交互郵件訪問協議)從服務器端獲取郵件。
SMTP協議一開始是基於純ASCII文本的,對於二進制文件(好比郵件附件中的圖像、聲音等)的處理並很差,因此後來新增MIME標準來編碼二進制文件,使其可以經過SMTP協議傳輸。
舉例來講,我給本身發封郵件,正文爲空,帶一個名爲hello.txt的附件,內容爲 您好!世界!
。導出郵件源碼,其關鍵部分以下圖所示:
MIME-Version: 1.0
:表示當前使用MIME標準1.0版本。
Content-Type: text/plain; name="hello.txt"
:表示附件文件名爲 hello.txt
,格式爲純文本。
Content-Transfer-Encoding: base64
:表示附件文件內容使用base64編碼後傳輸。
5oKo5aW977yM5LiW55WM77yB
:則是文件內容 您好,世界!
Base64編碼後的結果。
不過,MIME使用的不是標準Base64編碼。
可能會有人在不理解Base64編碼的狀況下,將其誤用於數據加密或數據校驗。
Base64是一種數據編碼方式,目的是讓數據符合傳輸協議的要求。標準Base64編碼解碼無需額外信息即徹底可逆,即便你本身自定義字符集設計一種類Base64的編碼方式用於數據加密,在多數場景下也較容易破解。
對於數據加密應該使用專門的目前尚未有效方式快速破解的加密算法。好比:對稱加密算法AES-128-CBC
,對稱加密須要密鑰,只要密鑰沒有泄露,一般難以破解;也可使用非對稱加密算法,如 RSA
,利用極大整數因數分解的計算量極大這一特色,使得使用公鑰加密的數據,只有使用私鑰才能快速解密。
對於數據校驗,也應該使用專門的消息認證碼生成算法,如 HMAC
- 一種使用單向散列函數構造消息認證碼的方法,其過程是不可逆的、惟一肯定的,而且使用密鑰來生成認證碼,其目的是防止數據在傳輸過程當中被篡改或僞造。將原始數據與認證碼一塊兒傳輸,數據接收端將原始數據使用相同密鑰和相同算法再次生成認證碼,與原有認證碼進行比對,校驗數據的合法性。
那麼針對各大網站被脫庫的問題,請問應該怎麼存儲用戶的登陸密碼?
答案是:在註冊時,根據用戶設置的登陸密碼,生成其消息認證碼,而後存儲用戶名和消息認證碼,不存儲原始密碼。每次用戶登陸時,根據登陸密碼,生成消息認證碼,與數據庫中存儲的消息認證碼進行比對,以確認是否爲有效用戶,這樣即便網站被脫庫,用戶的原始密碼也不會泄露,不會爲用戶使用的其餘網站帶來帳號風險。
固然,使用的消息認證碼算法其哈希碰撞的機率應該極低才行,目前通常在HMAC算法中使用SHA256。對於這種方式須要注意一點:防止用戶使用弱密碼,不然也可能會被暴力破解。如今的網站通常要求用戶密碼6個字符以上,而且同時有數字和大小寫字母,甚至要求有特殊字符。
另外,也可使用加入隨機salt的哈希算法來存儲校驗用戶密碼。這裏暫不細述。
Base64兼顧字符集大小和編碼後數據長度,而且能夠靈活替換字符集的最後兩個字符,以應對多樣的需求,使其適用場景很是普遍。
固然,不少場景下有多種編碼方式可選擇,並不是Base64編碼不可,視需求,權衡利弊而定。