2018-04-24 Base64 + Base58編碼

Base64
Base58

Base64

Base64是一種用64個(gè)字符表示任意二進(jìn)制數(shù)據(jù)的方法橡娄。

編碼前诗箍,可以是各種各樣的字符,中文挽唉、法語(yǔ)滤祖、日語(yǔ)等,先把這些字符轉(zhuǎn)成字節(jié)瓶籽,然后對(duì)這些字節(jié)進(jìn)行編碼匠童,編碼后就都是限定了的64個(gè)字符了。

好處是編碼后文本數(shù)據(jù)可以顯示出來(lái)了塑顺,可以用在郵件汤求、網(wǎng)頁(yè)、URL上严拒。
編碼后扬绪,3字節(jié)的二進(jìn)制數(shù)變成了4字節(jié)的文本數(shù)據(jù),長(zhǎng)度增加了33%裤唠,所以適合傳送少量二進(jìn)制數(shù)據(jù)挤牛。

編碼

首先準(zhǔn)備一個(gè)含有64個(gè)字符的數(shù)組,一般是 “A~Za~z0~9+/”种蘸;
然后原先的二進(jìn)制數(shù)據(jù)每3個(gè)字節(jié)一組墓赴,就是3*8=24個(gè)比特,然后分成四組航瞭,每組6個(gè)比特诫硕,每6個(gè)比特就對(duì)應(yīng)了一個(gè)0~63之間的數(shù)字;
把這4個(gè)范圍在0~63的數(shù)字當(dāng)作索引刊侯,在上面的字符數(shù)組里查找相應(yīng)的字符章办;
這樣就是編碼后的字符串了。

特殊處理:
如果要編碼的二進(jìn)制數(shù)據(jù)不是3的倍數(shù)滨彻,就用\x00字節(jié)在末尾補(bǔ)足藕届,然后再在編碼的末尾加上1到2個(gè)等號(hào)(=),表示補(bǔ)了多少字節(jié)疮绷,這樣解碼的時(shí)候就可以自動(dòng)去掉了翰舌。

特別注意嚣潜,Base64編碼后的文本的長(zhǎng)度總是4的倍數(shù)冬骚,但是如果再加上1到2個(gè)=不就不是4的倍數(shù)了嗎?
所以并不是先編碼,再加上1到2個(gè)=只冻,而是編碼之后庇麦,把最后的1到2個(gè)字符(這個(gè)字符肯定是A替換=

解碼

與編碼相反,首先去除末尾的等號(hào)(=)喜德,然后比對(duì)初始的64字符的數(shù)組山橄,把編碼后的文本轉(zhuǎn)成各字符在數(shù)組里的索引值,再然后轉(zhuǎn)成6比特的二進(jìn)制數(shù)舍悯,最后刪除多余的\x00航棱。

改進(jìn)

  1. 標(biāo)準(zhǔn)Base64里是包含 +/ 的,在URL里不能直接作為參數(shù)萌衬,所以出現(xiàn)一種 “url safe” 的Base64編碼饮醇,其實(shí)就是把 +/ 替換成 -_
  2. 同樣的秕豫,=也會(huì)被誤解朴艰,所以編碼后干脆去掉=,解碼時(shí)混移,自動(dòng)添加一定數(shù)量的等號(hào)祠墅,使得其長(zhǎng)度為4的倍數(shù)即可正常解碼了。

思考

編碼時(shí)歌径,=添加的邏輯是:原始二進(jìn)制數(shù)據(jù)的長(zhǎng)度不是3的倍數(shù)時(shí)毁嗦,要補(bǔ)\x00,補(bǔ)了多少個(gè)\x00沮脖,就在編碼的末尾添加幾個(gè)=金矛;
而解碼時(shí),自動(dòng)添加=的邏輯是:把編碼后的文本的長(zhǎng)度補(bǔ)齊到4的倍數(shù)勺届。
這兩個(gè)的邏輯明明是不同的驶俊,為什么可以工作呢?

解答

因?yàn)檫@里的=的意義只在于表示編碼時(shí)添加了多少個(gè)\x00免姿,哪怕缺失了=饼酿,也可以知道缺失了多少個(gè),那么在解碼后就要去掉多少個(gè)\x00胚膊。
而且編碼時(shí)添加的=其實(shí)是替換故俐,并不會(huì)破壞編碼后長(zhǎng)度為4的倍數(shù)的這個(gè)特性,所以解碼時(shí)紊婉,把=換成A药版,然后正常解碼,再刪除掉尾部的X個(gè)\x00即可(X=1或著2)喻犁。

Base58

除了Base64槽片,還有Base16何缓、Base32、Base58还栓、Base85等編碼方式碌廓,這里介紹一下Base58是因?yàn)椋?/p>

  1. 它的編碼思路和Base64看起來(lái)不太一樣(雖然實(shí)質(zhì)是一樣的)
  2. 刪除了 + / 0 O I l,讓編碼后的結(jié)果更加清晰剩盒,且不容易看錯(cuò)
  3. 比特幣谷婆、Monero、Ripple辽聊、Flickr都在用這個(gè)Base58的編碼方式

Base58的本質(zhì)就是把256進(jìn)制的值轉(zhuǎn)成58進(jìn)制的值纪挎。
所以它在編碼時(shí)不需要考慮補(bǔ)\x00的問(wèn)題,直接轉(zhuǎn)換即可跟匆。
把字節(jié)流轉(zhuǎn)成一個(gè)256進(jìn)制的大數(shù)廷区,然后不斷除以58,保留余數(shù)贾铝,最后余數(shù)當(dāng)作索引隙轻,再倒序,即為轉(zhuǎn)換后的結(jié)果垢揩。

特殊處理:
不同于一個(gè)普通的數(shù)字轉(zhuǎn)成某個(gè)進(jìn)制玖绿,普通數(shù)字最高位是不會(huì)為0的,而我們要編碼的對(duì)象是字節(jié)流叁巨,那么如果字節(jié)流的最前面是0(\x00)斑匪,那么就會(huì)丟失這個(gè)信息。所以編碼時(shí)要特殊記錄一下锋勺,字節(jié)流的開端有多少個(gè)\x00蚀瘸,就直接在轉(zhuǎn)換后的編碼前面加上多少個(gè)b58Alphabet[0],同理庶橱,解碼的時(shí)候先記錄一下前面的b58Alphabet[0]的個(gè)數(shù)贮勃,然后解碼之后再在解碼的前面加上相同數(shù)量的0x00

示例代碼

base58.go

var b58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")

// Base58Encode encodes a byte array to Base58
func Base58Encode(input []byte) []byte {
    var result []byte

    x := big.NewInt(0).SetBytes(input)

    base := big.NewInt(int64(len(b58Alphabet)))
    zero := big.NewInt(0)
    mod := &big.Int{}

    for x.Cmp(zero) != 0 {
        x.DivMod(x, base, mod)
        result = append(result, b58Alphabet[mod.Int64()])
    }

    ReverseBytes(result)
    for _, b := range input {
        if b == 0x00 {
            result = append([]byte{b58Alphabet[0]}, result...)
        } else {
            break
        }
    }

    return result
}

// Base58Decode decodes Base58-encoded data
func Base58Decode(input []byte) []byte {
    result := big.NewInt(0)
    zeroBytes := 0

    for _, b := range input {
        if b == b58Alphabet[0] {
            zeroBytes++
        } else {
            break
        }
    }

    payload := input[zeroBytes:]
    for _, b := range payload {
        charIndex := bytes.IndexByte(b58Alphabet, b)
        result.Mul(result, big.NewInt(58))
        result.Add(result, big.NewInt(int64(charIndex)))
    }

    decoded := result.Bytes()
    decoded = append(bytes.Repeat([]byte{byte(0x00)}, zeroBytes), decoded...)

    return decoded
}

base58_test.go

func TestBase58Encode(t *testing.T) {
    data := []byte{0x00, 0x00, 0x00}
    encoded := Base58Encode(data)
    assert.Equal(t, []byte{'1', '1', '1'}, encoded)

    data = []byte{'a'}
    encoded = Base58Encode(data)
    assert.Equal(t, []byte{'2', 'g'}, encoded)

    data = []byte{'1', 0x00}
    encoded = Base58Encode(data)
    assert.Equal(t, []byte{'4', 'j', 'H'}, encoded)
}

func TestBase58Decode(t *testing.T) {
    data := []byte{0x00, 0x00, 0x00}
    assert.Equal(t, data, Base58Decode(Base58Encode(data)))

    data = []byte{'a'}
    assert.Equal(t, data, Base58Decode(Base58Encode(data)))

    data = []byte{'1', 0x00}
    assert.Equal(t, data, Base58Decode(Base58Encode(data)))
}

總結(jié)

不管是Base64還是Base58苏章,都會(huì)造成信息的冗余寂嘉,使得需要傳輸?shù)臄?shù)據(jù)量增大,所以不會(huì)用在很大的數(shù)據(jù)上枫绅。

  1. 使用Base64最普遍的是URL泉孩、郵件文本、圖片并淋;
  2. 相比于Base64直接切割比特的方法(3個(gè)比特變?yōu)?個(gè)比特)寓搬,Base58采用的大數(shù)進(jìn)制轉(zhuǎn)換,效率更低县耽,所以使用場(chǎng)景的數(shù)據(jù)更少句喷,例如上面提到的比特幣的地址的編碼曼尊。

參考文檔

  1. https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001431954588961d6b6f51000ca4279a3415ce14ed9d709000
  2. http://www.reibang.com/p/e002931bb38b
  3. https://github.com/Jeiwan/blockchain_go/blob/part_7/base58.go (這里的代碼有問(wèn)題)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市脏嚷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瞒御,老刑警劉巖父叙,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異肴裙,居然都是意外死亡趾唱,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蜻懦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)甜癞,“玉大人,你說(shuō)我怎么就攤上這事宛乃∮圃郏” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵征炼,是天一觀的道長(zhǎng)析既。 經(jīng)常有香客問(wèn)我,道長(zhǎng)谆奥,這世上最難降的妖魔是什么眼坏? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮酸些,結(jié)果婚禮上宰译,老公的妹妹穿的比我還像新娘。我一直安慰自己魄懂,他們只是感情好沿侈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著市栗,像睡著了一般肋坚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肃廓,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天智厌,我揣著相機(jī)與錄音,去河邊找鬼盲赊。 笑死铣鹏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哀蘑。 我是一名探鬼主播诚卸,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼葵第,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了合溺?” 一聲冷哼從身側(cè)響起卒密,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎棠赛,沒(méi)想到半個(gè)月后哮奇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睛约,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年鼎俘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辩涝。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贸伐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怔揩,到底是詐尸還是另有隱情捉邢,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布商膊,位于F島的核電站歌逢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翘狱。R本人自食惡果不足惜秘案,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望潦匈。 院中可真熱鬧阱高,春花似錦、人聲如沸茬缩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凰锡。三九已至未舟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掂为,已是汗流浹背裕膀。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勇哗,地道東北人昼扛。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像欲诺,于是被迫代替她去往敵國(guó)和親抄谐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渺鹦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 字符是用戶可以讀寫的最小單位毅厚。計(jì)算機(jī)所能支持的字符組成的集合,就叫做字符集浦箱。字符集通常以二維表的形式存在吸耿。二維表的...
    劉惜有閱讀 8,109評(píng)論 2 14
  • 最近學(xué)Python的時(shí)候遇到了編碼問(wèn)題,順帶了解了一下base64編碼憎茂,首先閱讀了咱CSDN上的一篇文章Base6...
    連命都給你了閱讀 861評(píng)論 0 3
  • 上一周聽了武老師有關(guān)夢(mèng)的主題分享,很有感觸锤岸,因?yàn)槲乙部偸呛荜P(guān)注我的夢(mèng)竖幔。其中夢(mèng)到底表達(dá)了什么,以及如何解夢(mèng)都是讓我很...
    學(xué)習(xí)思考行動(dòng)閱讀 752評(píng)論 2 1
  • 臨夏最淳樸的年味_搖錢棗樹是偷!臨夏方言中棗樹與早富諧音拳氢。過(guò)年插搖錢棗樹是雷打不動(dòng)的春節(jié)習(xí)俗。寓意來(lái)年五谷豐登蛋铆,早日致...
    金手銀胳膊閱讀 674評(píng)論 0 1
  • 額房間里有兩個(gè)小門 貼著地面 只能讓小貓通過(guò) 我按下手中按鈕 兩個(gè)門同時(shí)打開 第一個(gè)門里爬出小螞蟻 ...
    是他631閱讀 152評(píng)論 0 0