golang實現aes-cbc-256加密解密過程記錄
我為什麼吃撐了要實現go的aes-cbc-256加密解密功能?
之前的專案是用php實現的,現在準備用go重構,需要用到這個功能,這麼常用的功能上網一搜一大把現成例子,於是基於go現有api分分鐘實現一對加密解密函式,你想得沒錯,一跑就失敗,好了不廢話了,go的aes-cbc實現由兩個限制
1:面臨兩個問題
1:go祕鑰長度必須是16/24/32
go原始碼如下,我們的祕鑰長度是72,不符合啊
// 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位怎麼遷移
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原始碼,並複製了出來,給個位看看先
//加密實現
TEXT ·encryptBlockAsm(SB),NOSPLIT,$
0
MOVQ nr+
0
(FP), CX
666
...
Lenc256:
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...
小弟:大哥,這原始碼好像有點不對勁啊
我去:彙編,強做鎮靜,先找一下相關資料,好久沒研究彙編的我,再次研究起了彙編,找了一些資料:ofollow,noindex" target="_blank">https://juejin.im/entry/5a39d646f265da431a435476 ,資料不錯就是看不懂
把部分程式碼拷出來,試著改了一下彙編程式碼,運行了一下,沒成功
其實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語言呢!