golang實現aes-cbc-256加密解密

我爲何吃撐了要實現go的aes-cbc-256加密解密功能?php

以前的項目是用php實現的,如今準備用go重構,須要用到這個功能,這麼經常使用的功能上網一搜一大把現成例子,因而基於go現有api分分鐘實現一對加密解密函數,你想得沒錯,一跑就失敗,好了不廢話了,go的aes-cbc實現由兩個限制git

1:面臨兩個問題

1:go祕鑰長度必須是16/24/32

go源碼以下,咱們的祕鑰長度是72,不符合啊github

// NewCipher creates and returns a new cipher.Block.
// The key argument should be the AES key,
// either 16, 24, or 32 bytes to select
// AES-128, AES-192, or AES-256.
func  NewCipher(key []byte) (cipher.Block, error) {
    k := len(key)
    switch  k {
    default :
       return  nil, KeySizeError(k)
    case  16 24 32 :
       break
    }
    return  newCipher(key)
}

2:go根本不支持256位的aes-cbc加密解密

好脾氣的我再次貼一下go的相關源碼,赫然寫着const BlockSize = 16,還他媽是個常量,也就是說go一次只能加密16*8=128位,個人php256位怎麼遷移golang

const  BlockSize =  16   //你一眼就看到這麼帥的我
 
type  aesCipherAsm  struct  {
    aesCipher
}
 
var  useAsm = cipherhw.AESGCMSupport()
 
func  newCipher(key []byte) (cipher.Block, error) {
    if  !useAsm {
       return  newCipherGeneric(key)
    }
    n := len(key) +  28
    c := aesCipherAsm{aesCipher{ make ([]uint32, n),  make ([]uint32, n)}}
    rounds :=  10
    switch  len(key) {
    case  128  8 :
       rounds =  10
    case  192  8 :
       rounds =  12
    case  256  8 :
       rounds =  14
    }
    expandKeyAsm(rounds, &key[ 0 ], &c.enc[ 0 ], &c.dec[ 0 ])
    if  hasGCMAsm() {
       return  &aesCipherGCM{c}, nil
    }
 
    return  &c, nil
}
 
func  (c *aesCipherAsm) BlockSize() int {  return  BlockSize }
 
func  (c *aesCipherAsm) Encrypt(dst, src []byte) {
    if  len(src) < BlockSize {
       panic( "crypto/aes: input not full block" )
    }
    if  len(dst) < BlockSize {
       panic( "crypto/aes: output not full block" )
    }
    encryptBlockAsm(len(c.enc)/ 4 - 1 , &c.enc[ 0 ], &dst[ 0 ], &src[ 0 ])
}
 
func  (c *aesCipherAsm) Decrypt(dst, src []byte) {
    if  len(src) < BlockSize {
       panic( "crypto/aes: input not full block" )
    }
    if  len(dst) < BlockSize {
       panic( "crypto/aes: output not full block" )
    }
    decryptBlockAsm(len(c.dec)/ 4 - 1 , &c.dec[ 0 ], &dst[ 0 ], &src[ 0 ])
}

2:哥開始思考了

問題一個個擊破,想辦法看能不能繞過去,因爲是在NewCipher的時候對必要長度作了限制,我本身new不就好了,一看傻眼了,只有接口是public,實現對象都是private的,要想實例化對象只能經過NewCipher,繞不過去啊,大不了我把你的源碼拷出來,本身在改改,再次衝進go源碼,並複製了出來,給個位看看先windows

//加密實現
TEXT ·encryptBlockAsm(SB),NOSPLIT,$ 0
     MOVQ nr+ 0 (FP), CX
     666 ...
Lenc256:
     MOVUPS  0 (AX), X1
     666 ...
Lenc196:
     MOVUPS  0 (AX), X1
     666 ...
Lenc128:
     MOVUPS  0 (AX), X1
     666 ..
     RET
 
//解密實現
// func decryptBlockAsm(nr int, xk *uint32, dst, src *byte)
TEXT ·decryptBlockAsm(SB),NOSPLIT,$ 0
     MOVQ nr+ 0 (FP), CX
     666 ...
Ldec256:
     MOVUPS  0 (AX), X1
     666 ...
Ldec196:
     MOVUPS  0 (AX), X1
     666 ...
Ldec128:
     MOVUPS  0 (AX), X1
     666 ...
     RET
 
//經過key和iv初始化加密解密所需的數據結構
// func expandKeyAsm(nr int, key *byte, enc, dec *uint32) {
// Note that round keys are stored in uint128 format, not uint32
TEXT ·expandKeyAsm(SB),NOSPLIT,$ 0
     MOVQ nr+ 0 (FP), CX
     JE Lexp_enc196    //徹底不知道是兩個什麼鬼命令
     JB Lexp_enc128    //同上,因此說是兩個鬼命令
Lexp_enc256:
     MOVUPS  16 (AX), X2
     666 ...

首長:同志們,跟我一塊兒喊:源碼在手,天下我有,666...api

小弟:大哥,這源碼好像有點不對勁啊數據結構

我去:彙編,強作鎮靜,先找一下相關資料,很久沒研究彙編的我,再次研究起了彙編,找了一些資料:https://juejin.im/entry/5a39d646f265da431a435476,資料不錯就是看不懂函數

把部分代碼拷出來,試着改了一下彙編代碼,運行了一下,沒成功ui

其實go也有非彙編實現的go代碼,但每次也是加密16字節,不符合要求,我要每次處理32字節的源碼,以後還嘗試過把NewCipher出的對象包一層,讓BlockSize()返回32,天然也是不行加密

第一階段以失敗了結

3:想用go調PHP

人有多大膽,go調毛PHP啊,上網一搜還真有這麼一位大膽的大神,實現了go調PHP:https://github.com/deuill/go-php,小弟我感受像是找到寶了,搞過來一跑,你還別說真成功了,當我看到go調PHP輸出hello world得那一刻,淚牛滿面,方案就這麼定了:go調php實現aes-cbc-256加密解密

以上成功只是幻想,實際上是go調c成功了,並不興奮,調php並無,滿電腦沒找到libphp.so,原來在編譯php的時候沒有生成這個lib庫,go調php就是想把php實現編譯到你的程序中讓你調用,因而又開始找資料,找到這個:https://github.com/taowen/go-php,好熟悉的名字,這是咱們公司的大神陶師傅啊,鄭重聲明:大神但是帶我作過項目的。因而厚着臉皮向大神請教,大神說不建議用go調PHP,這條路不太靠譜,建議直接rpc調用,當我告訴大神個人需求和go的現狀時,大神建議:把代碼從標準庫拷出來,兩邊對照着調試,你是大神仍是我是大神,讓我用go把c的aes-cbc-256重新實現,我怎麼可能作獲得!固然你是大神,我照作

4:golang實現aes-cbc-256加密解密正式開始

第一步看PHP源碼。按照入口一步步看下去,主要是如下幾個函數

mcrypt_module_open
mcrypt_generic_init
mcrypt_generic
mdecrypt_generic

實現都在PHP的擴展模塊mcrypt中,這個模塊也是隻是對另外一標準庫的封裝,地址:https://sourceforge.net/projects/mcrypt/files/Libmcrypt/,因而把代碼下下來看,代碼還挺多,因爲我只須要實現aes-cbc-256,其餘的直接略過,最終發現我只須要關注兩個文件:modules/algorithms/rijndael-256.c,modules/modes/cbc.c,各位觀衆有沒有發現這個標準庫的命名很給力,幾遍下來發現並不複雜,總共代碼不到600+行,因而將代碼複製過來,開始將c語言翻譯成go語言,非常當心翼翼,一回兒的功夫就翻譯完了(其實用了兩個多小時),翻譯很快那是相對debug階段來講的,一加密發現不對,也不知道錯在哪,代碼都快看吐了,都沒發現問題,因而只好按照大神說的兩邊對比調試,c語言已經兩年多沒搞了,因而安裝了Clion,簡單研究了一下,因爲以前是搞windows的,mac上也沒搞過,還好挺好用,開始也是編譯不過,因而簡單複習了一下c語言,最後終於跑通了,因爲libmcrypt的實現處處都是指針,不少數據都看不到,只能打印出來看,後來發現,祕鑰長度搞錯了,我是傳的32,其實祕鑰長度是這麼計算的

//獲取加密key長度
func  getKeySize(size int) int {
    for  _, val :=  range  keySizes {
       if  size <= val {
          return  val
       }
    }
    return  BLOCK_SIZE
}

搞了很久終於解決了加密問題,我那個喜啊(聽不懂就當是方言吧),真的特別有成就感,而後就開始搞解密,發現不對,又是半天找不到緣由,在這個過程當中又找了一個庫:https://github.com/mfpierre/go-mcrypt,這個庫實現了go的各類加密解密,其實只是對c標準庫mcrypt的封裝,考慮到線上環境不必定有,或是環境不同,就沒考慮這個庫,我他媽褲子都脫了(實現了一半加密),你讓我放棄。還有這個庫幹嗎要對祕鑰長度進行限制,標準庫自己沒有任何限制好很差。

5:含着淚也要解決問題

實現完加密的時候,我就向大神吹牛說,我已經實現,如今解密沒解決,怎麼辦?

又是一陣看代碼,沒發現任何問題,只好使出終極殺手鐗:單步對比調試,其實以前已經發現static word32 rtable[256];初始化不對了,爲何加密能成解密就不行,這個變量還真是隻在解密用到,同步對比調試終於發現了問題,一個go語言不一樣於c語言的問題,且看下面這個函數:

//c語言實現
static  byte bmul(byte x, byte y)
{        
    if  (x && y)
       return  ptab[(ltab[x] + ltab[y]) % 255];
    else
       return  0;
}
bmul(200,200) == 145
 
//go語言實現
func bmul(x, y byte) byte {
    if  x > 0 && y > 0 {
       return  ptab[(ltab[x]+ltab[y])%255]
    }
    return  0
}
bmul(200,200) == 144

朋友們啊,看到區別沒有,前面說了,我是把c語言直接翻譯成go語言的,可是c語言和go語言不同啊,兩個徹底同樣的函數,居然不同,c語言400%255=145好理解,go怎麼就變成144了呢,200+200=144,咱們來看看400的二進制表示110010000,去掉最前面的1,就是010010000,恰好144,也就是說c語言byte超過了255根本不要緊,而go超過了255就給截斷了,說好的互聯網時代的c語言呢!

6:最後厚顏無恥的掛到了github.com

https://github.com/chentaihan/aesCbc

相關文章
相關標籤/搜索