1. 程式人生 > >Go加密演算法總結

Go加密演算法總結

#### 前言 > 加密解密在實際開發中應用比較廣泛,常用加解密分為:“**對稱式**”、“**非對稱式**”和”**數字簽名**“。 > > **對稱式**:對稱加密(也叫[私鑰](https://baike.baidu.com/item/私鑰)加密)指加密和解密使用相同[金鑰](https://baike.baidu.com/item/金鑰)的加密演算法。具體演算法主要有[DES演算法](https://baike.baidu.com/item/DES演算法),[3DES](https://baike.baidu.com/item/3DES)演算法,TDEA演算法,[Blowfish](https://baike.baidu.com/item/Blowfish)演算法,[RC5](https://baike.baidu.com/item/RC5)演算法,[IDEA](https://baike.baidu.com/item/IDEA)演算法。 > > **非對稱加密(公鑰加密)**:指加密和解密使用不同[金鑰](https://baike.baidu.com/item/金鑰)的加密演算法,也稱為公[私鑰](https://baike.baidu.com/item/私鑰)加密。具體演算法主要有[RSA](https://baike.baidu.com/item/RSA)、[Elgamal](https://baike.baidu.com/item/Elgamal)、揹包演算法、Rabin、D-H、[ECC](https://baike.baidu.com/item/ECC)(橢圓曲線加密演算法)。 > > 數字簽名:數字簽名是非對稱[金鑰加密技術](https://baike.baidu.com/item/金鑰加密技術)與[數字摘要](https://baike.baidu.com/item/數字摘要/4069118)技術的應用。主要演算法有md5、hmac、sha1等。 > > 以下介紹golang語言主要的加密解密演算法實現。 #### md5 > **MD5資訊摘要演算法**是一種被廣泛使用的[密碼雜湊函式](https://baike.baidu.com/item/密碼雜湊函式/14937715),可以產生出一個128位(16進位制,32個字元)的雜湊值(hash value),用於確保資訊傳輸完整一致。 ```go func GetMd5String(s string) string { h := md5.New() h.Write([]byte(s)) return hex.EncodeToString(h.Sum(nil)) } ``` #### hmac > HMAC是金鑰相關的雜湊運算訊息認證碼(Hash-based Message Authentication Code)的縮寫, > > 它通過一個標準演算法,在計算雜湊的過程中,把key混入計算過程中。 > > 和我們自定義的加salt演算法不同,Hmac演算法針對所有雜湊演算法都通用,無論是MD5還是SHA-1。採用Hmac替代我們自己的salt演算法,可以使程式演算法更標準化,也更安全。 ```go //key隨意設定 data 要加密資料 func Hmac(key, data string) string { hash:= hmac.New(md5.New, []byte(key)) // 建立對應的md5雜湊加密演算法 hash.Write([]byte(data)) return hex.EncodeToString(hash.Sum([]byte(""))) } func HmacSha256(key, data string) string { hash:= hmac.New(sha256.New, []byte(key)) //建立對應的sha256雜湊加密演算法 hash.Write([]byte(data)) return hex.EncodeToString(hash.Sum([]byte(""))) } ``` #### sha1 > SHA-1可以生成一個被稱為訊息摘要的160[位](https://baike.baidu.com/item/位)(20[位元組](https://baike.baidu.com/item/位元組))雜湊值,雜湊值通常的呈現形式為40個[十六進位制](https://baike.baidu.com/item/十六進位制/4162457)數。 ```go func Sha1(data string) string { sha1 := sha1.New() sha1.Write([]byte(data)) return hex.EncodeToString(sha1.Sum([]byte(""))) } ``` ##### Crypto加密 ```go package main import ( "fmt" "golang.org/x/crypto/bcrypt" ) func main() { password := "test" hash,err := bcrypt.GenerateFromPassword([]byte(password),0) fmt.Println(string(hash),err) // 密碼如果校驗成功會返回Nil fmt.Println(bcrypt.CompareHashAndPassword(hash,[]byte("youmen18"))) } ``` #### AES > 密碼學中的高階加密標準(Advanced Encryption Standard,AES),又稱Rijndael[加密法](https://baike.baidu.com/item/加密法),是美國聯邦政府採用的一種區塊加密標準。這個標準用來替代原先的[DES](https://baike.baidu.com/item/DES)(Data Encryption Standard),已經被多方分析且廣為全世界所使用。AES中常見的有三種解決方案,分別為AES-128、AES-192和AES-256。如果採用真正的128位加密技術甚至256位加密技術,蠻力攻擊要取得成功需要耗費相當長的時間。 `AES有五種加密模式` ```go /* 電碼本模式(Electronic Codebook Book (ECB))、 密碼分組連結模式(Cipher Block Chaining (CBC))、 計算器模式(Counter (CTR))、 密碼反饋模式(Cipher FeedBack (CFB)) 輸出反饋模式(Output FeedBack (OFB)) */ ``` ##### ECB模式 `出於安全考慮,golang預設並不支援ECB模式。` ```go package main import ( "crypto/aes" "fmt" ) func AESEncrypt(src []byte, key []byte) (encrypted []byte) { cipher, _ := aes.NewCipher(generateKey(key)) length := (len(src) + aes.BlockSize) / aes.BlockSize plain := make([]byte, length*aes.BlockSize) copy(plain, src) pad := byte(len(plain) - len(src)) for i := len(src); i < len(plain); i++ { plain[i] = pad } encrypted = make([]byte, len(plain)) // 分組分塊加密 for bs, be := 0, cipher.BlockSize(); bs <= len(src); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { cipher.Encrypt(encrypted[bs:be], plain[bs:be]) } return encrypted } func AESDecrypt(encrypted []byte, key []byte) (decrypted []byte) { cipher, _ := aes.NewCipher(generateKey(key)) decrypted = make([]byte, len(encrypted)) // for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() { cipher.Decrypt(decrypted[bs:be], encrypted[bs:be]) } trim := 0 if len(decrypted) > 0 { trim = len(decrypted) - int(decrypted[len(decrypted)-1]) } return decrypted[:trim] } func generateKey(key []byte) (genKey []byte) { genKey = make([]byte, 16) copy(genKey, key) for i := 16; i < len(key); { for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 { genKey[j] ^= key[i] } } return genKey } func main() { source:="hello world" fmt.Println("原字元:",source) //16byte金鑰 key:="1443flfsaWfdas" encryptCode:=AESEncrypt([]byte(source),[]byte(key)) fmt.Println("密文:",string(encryptCode)) decryptCode:=AESDecrypt(encryptCode,[]byte(key)) fmt.Println("解密:",string(decryptCode)) } ``` ##### CBC模式 ```go package main import( "bytes" "crypto/aes" "fmt" "crypto/cipher" "encoding/base64" ) func main() { orig := "hello world" key := "0123456789012345" fmt.Println("原文:", orig) encryptCode := AesEncrypt(orig, key) fmt.Println("密文:" , encryptCode) decryptCode := AesDecrypt(encryptCode, key) fmt.Println("解密結果:", decryptCode) } func AesEncrypt(orig string, key string) string { // 轉成位元組陣列 origData := []byte(orig) k := []byte(key) // 分組祕鑰 // NewCipher該函式限制了輸入k的長度必須為16, 24或者32 block, _ := aes.NewCipher(k) // 獲取祕鑰塊的長度 blockSize := block.BlockSize() // 補全碼 origData = PKCS7Padding(origData, blockSize) // 加密模式 blockMode := cipher.NewCBCEncrypter(block, k[:blockSize]) // 建立陣列 cryted := make([]byte, len(origData)) // 加密 blockMode.CryptBlocks(cryted, origData) return base64.StdEncoding.EncodeToString(cryted) } func AesDecrypt(cryted string, key string) string { // 轉成位元組陣列 crytedByte, _ := base64.StdEncoding.DecodeString(cryted) k := []byte(key) // 分組祕鑰 block, _ := aes.NewCipher(k) // 獲取祕鑰塊的長度 blockSize := block.BlockSize() // 加密模式 blockMode := cipher.NewCBCDecrypter(block, k[:blockSize]) // 建立陣列 orig := make([]byte, len(crytedByte)) // 解密 blockMode.CryptBlocks(orig, crytedByte) // 去補全碼 orig = PKCS7UnPadding(orig) return string(orig) } //補碼 //AES加密資料塊分組長度必須為128bit(byte[16]),金鑰長度可以是128bit(byte[16])、192bit(byte[24])、256bit(byte[32])中的任意一個。 func PKCS7Padding(ciphertext []byte, blocksize int) []byte { padding := blocksize - len(ciphertext)%blocksize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } //去碼 func PKCS7UnPadding(origData []byte) []byte { length := len(origData) unpadding := int(origData[length-1]) return origData[:(length - unpadding)] } ``` ##### CRT模式 ```go package main import ( "bytes" "crypto/aes" "crypto/cipher" "fmt" ) //加密 func aesCtrCrypt(plainText []byte, key []byte) ([]byte, error) { //1. 建立cipher.Block介面 block, err := aes.NewCipher(key) if err != nil { return nil, err } //2. 建立分組模式,在crypto/cipher包中 iv := bytes.Repeat([]byte("1"), block.BlockSize()) stream := cipher.NewCTR(block, iv) //3. 加密 dst := make([]byte, len(plainText)) stream.XORKeyStream(dst, plainText) return dst, nil } func main() { source:="hello world" fmt.Println("原字元:",source) key:="1443flfsaWfdasds" encryptCode,_:=aesCtrCrypt([]byte(source),[]byte(key)) fmt.Println("密文:",string(encryptCode)) decryptCode,_:=aesCtrCrypt(encryptCode,[]byte(key)) fmt.Println("解密:",string(decryptCode)) } ``` ##### CFB模式 ```go package main import ( "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "fmt" "io" ) func AesEncryptCFB(origData []byte, key []byte) (encrypted []byte) { block, err := aes.NewCipher(key) if err != nil { //panic(err) } encrypted = make([]byte, aes.BlockSize+len(origData)) iv := encrypted[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { //panic(err) } stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(encrypted[aes.BlockSize:], origData) return encrypted } func AesDecryptCFB(encrypted []byte, key []byte) (decrypted []byte) { block, _ := aes.NewCipher(key) if len(encrypted) < aes.BlockSize { panic("ciphertext too short") } iv := encrypted[:aes.BlockSize] encrypted = encrypted[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(encrypted, encrypted) return encrypted } func main() { source:="hello world" fmt.Println("原字元:",source) key:="ABCDEFGHIJKLMNO1"//16位 encryptCode:=AesEncryptCFB([]byte(source),[]byte(key)) fmt.Println("密文:",hex.EncodeToString(encryptCode)) decryptCode:=AesDecryptCFB(encryptCode,[]byte(key)) fmt.Println("解密:",string(decryptCode)) } ``` ##### OFB模式 ```go package main import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "encoding/hex" "fmt" "io" ) func aesEncryptOFB( data[]byte,key []byte) ([]byte, error) { data = PKCS7Padding(data, aes.BlockSize) block, _ := aes.NewCipher([]byte(key)) out := make([]byte, aes.BlockSize + len(data)) iv := out[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { return nil, err } stream := cipher.NewOFB(block, iv) stream.XORKeyStream(out[aes.BlockSize:], data) return out, nil } func aesDecryptOFB( data[]byte,key []byte) ([]byte, error) { block, _ := aes.NewCipher([]byte(key)) iv := data[:aes.BlockSize] data = data[aes.BlockSize:] if len(data) % aes.BlockSize != 0 { return nil, fmt.Errorf("data is not a multiple of the block size") } out := make([]byte, len(data)) mode := cipher.NewOFB(block, iv) mode.XORKeyStream(out, data) out= PKCS7UnPadding(out) return out, nil } //補碼 //AES加密資料塊分組長度必須為128bit(byte[16]),金鑰長度可以是128bit(byte[16])、192bit(byte[24])、256bit(byte[32])中的任意一個。 func PKCS7Padding(ciphertext []byte, blocksize int) []byte { padding := blocksize - len(ciphertext)%blocksize padtext := bytes.Repeat([]byte{byte(padding)}, padding) return append(ciphertext, padtext...) } //去碼 func PKCS7UnPadding(origData []byte) []byte { length := len(origData) unpadding := int(origData[length-1]) return origData[:(length - unpadding)] } func main() { source:="hello world" fmt.Println("原字元:",source) key:="1111111111111111"//16位 32位均可 encryptCode,_:=aesEncryptOFB([]byte(source),[]byte(key)) fmt.Println("密文:",hex.EncodeToString(encryptCode)) decryptCode,_:=aesDecryptOFB(encryptCode,[]byte(key)) fmt.Println("解密:",string(decryptCode)) } ``` ##### RSA加密簡介 ##### rsa加密演算法簡史 >
RSA是1977年由羅納德·李維斯特(Ron Rivest)、阿迪·薩莫爾(Adi Shamir)和倫納德·阿德曼(Leonard Adleman)一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們三人姓氏開頭字母拼在一起組成的。 ##### rsa加密演算法實現原理 > 學過演算法的朋友都知道,計算機中的演算法其實就是數學運算。所以,再講解RSA加密演算法之前,有必要了解一下一些必備的數學知識。我們就從數學知識開始講解。 ##### 必備數學知識 > RSA加密演算法中,只用到**素數、互質數、指數運算、模運算**等幾個簡單的數學知識。所以,我們也需要了解這幾個概念即可 ##### 素數 >
素數又稱質數,指在一個大於1的自然數中,除了1和此整數自身外,不能被其他自然數整除的數。這個概念,我們在上初中,甚至小學的時候都學過了,這裡就不再過多解釋了。 ##### 互質數 > 百度百科上的解釋是:公因數只有1的兩個數,叫做互質數。;維基百科上的解釋是:互質,又稱互素。若N個整數的最大公因子是1,則稱這N個整數互質。 `常見的互質數判斷方法主要有以下幾種:` ```go /* 1、兩個不同的質數一定是互質數。例如,2與7、13與19。 2、一個質數,另一個不為它的倍數,這兩個數為互質數。例如,3與10、5與 26。 3、相鄰的兩個自然數是互質數。如 15與 16。 4、相鄰的兩個奇數是互質數。如 49與 51。 5、較大數是質數的兩個數是互質數。如97與88。 6、小數是質數,大數不是小數的倍數的兩個數是互質數。例如 7和 16。 7、2和任何奇數是互質數。例如2和87。 8、1不是質數也不是合數,它和任何一個自然數在一起都是互質數。如1和9908。 9、輾轉相除法。 */ ``` ##### 指數運算 ```go /*  指數運算又稱乘方計算,計算結果稱為冪。nm指將n自乘m次。把nm看作乘方的結果,叫做”n的m次冪”或”n的m次方”。其中,n稱為“底數”,m稱為“指數”。 */ ``` ##### 模運算 ```go /* 模運算即求餘運算。“模”是“Mod”的音譯。和模運算緊密相關的一個概念是“同餘”。數學上,當兩個整數除以同一個正整數,若得相同餘數,則二整數同餘。   兩個整數a,b,若它們除以正整數m所得的餘數相等,則稱a,b對於模m同餘,記作: a ≡ b (mod m);讀作:a同餘於b模m,或者,a與b關於模m同餘。例如:26 ≡ 14 (mod 12)。 */ ``` #### RSA加密演算法 ##### 公鑰和金鑰的產生 >
假設Alice想要通過一個不可靠的媒體接收Bob的一條私人訊息。她可以用以下的方式來產生一個公鑰和一個私鑰: ```go /* 1、隨意選擇兩個大的質數p和q,p不等於q,計算N=pq。 2、根據尤拉函式,求得r = (p-1)(q-1) 3、選擇一個小於 r 的整數 e,求得 e 關於模 r 的模反元素,命名為d。(模反元素存在,當且僅當e與r互質) 4、將 p 和 q 的記錄銷燬。 (N,e)是公鑰,(N,d)是私鑰。Alice將她的公鑰(N,e)傳給Bob,而將她的私鑰(N,d)藏起來。 */ ``` ##### **加密訊息** > 假設Bob想給Alice送一個訊息m,他知道Alice產生的N和e。他使用起先與Alice約好的格式將m轉換為一個小於N的整數n,比如他可以將每一個字轉換為這個字的Unicode碼,然後將這些數字連在一起組成一個數字。假如他的資訊非常長的話,他可以將這個資訊分為幾段,然後將每一段轉換為n。用下面這個公式他可以將n加密為c: > >   `ne ≡ c (mod N)` > > 計算c並不複雜。Bob算出c後就可以將它傳遞給Alice。 ##### 解密訊息 > Alice得到Bob的訊息c後就可以利用她的金鑰d來解碼。她可以用以下這個公式來將c轉換為n: > >   `cd ≡ n (mod N)` > > 得到n後,她可以將原來的資訊m重新復原。 ##### 解碼的原理是 >   `cd ≡ n e·d(mod N)` > > 以及`ed ≡ 1 (mod p-1)`和`ed ≡ 1 (mod q-1)。`由費馬小定理可證明(因為p和q是質數) > >   `n e·d ≡ n (mod p)`  和  `n e·d ≡ n (mod q)` > > 這說明(因為p和q是不同的質數,所以p和q互質) > >   `n e·d ≡ n (mod pq)` ##### 簽名訊息 > RSA也可以用來為一個訊息署名。假如甲想給乙傳遞一個署名的訊息的話,那麼她可以為她的訊息計算一個雜湊值(Message digest),然後用她的金鑰(private key)加密這個雜湊值並將這個“署名”加在訊息的後面。這個訊息只有用她的公鑰才能被解密。乙獲得這個訊息後可以用甲的公鑰解密這個雜湊值,然後將這個資料與他自己為這個訊息計算的雜湊值相比較。假如兩者相符的話,那麼他就可以知道發信人持有甲的金鑰,以及這個訊息在傳播路徑上沒有被篡改過。 #### Golang加密解密之RSA ##### 概要 > 這是一個非對稱加密演算法,一般通過公鑰加密,私鑰解密。 > > 在加解密過程中,使用openssl生產金鑰。執行如下操作: ##### 建立私鑰 ```go openssl genrsa -out private.pem 1024 //金鑰長度,1024覺得不夠安全的話可以用2048,但是代價也相應增大 ``` ##### 建立公鑰 ```go openssl rsa -in private.pem -pubout -out public.pem // 這樣便生產了金鑰。 ``` > 一般地,各個語言也會提供API,用於生成金鑰。在Go中,可以檢視`encoding/pem`包和`crypto/x509`包。 > > 加密解密這塊,涉及到很多標準 ##### Go RSA加密 1. rsa加解密, 必須會去查crypto/ras這個包 ```go Package rsa implements RSA encryption as specified in PKCS#1. ``` > 這是該包的說明:實現RSA加密技術,基於PKCS#1規範。 > > 對於什麼是PKCS#1,可以查閱相關資料。PKCS(公鑰密碼標準),而#1就是RSA的標準。可以檢視:PKCS系列簡介 > > 從該包中函式的名稱,可以看到有兩對加解密的函式。 ```go EncryptOAEP和DecryptOAEP EncryptPKCS1v15和DecryptPKCS1v15 ``` > 這稱作加密方案,詳細可以檢視,PKCS #1 v2.1 RSA 演算法標準 > > 可見,當與其他語言互動時,需要確定好使用哪種方案。 `PublicKey和PrivateKey兩個型別分別代表公鑰和私鑰,關於這兩個型別中成員該怎麼設定,這涉及到RSA加密演算法,本文中,這兩個型別的例項通過解析文章開頭生成的金鑰得到。` 2 . 解析金鑰得到PublicKey和PrivateKey的例項 > 這個過程,我也是花了好些時間(主要對各種加密的各種東東不熟):怎麼將**openssl**生成的金鑰檔案解析到公鑰和私鑰例項呢? > > 在`encoding/pem`包中,看到了—–BEGIN Type—–這樣的字樣,這正好和**openssl**生成的金鑰形式差不多,那就試試。 > > 在該包中,一個block代表的是PEM編碼的結構,關於PEM,請查閱相關資料。我們要解析金鑰,當然用**Decode**方法: ```go /* func Decode(data []byte) (p *Block, rest []byte) */ ``` > 這樣便得到了一個Block的例項(指標)。 > > 解析來看`crypto/x509`。為什麼是x509呢?這又涉及到一堆概念。先不管這些,我也是看**encoding**和**crypto**這兩個包的子包摸索出來的。 `在x509包中,有一個函式:` ```go func ParsePKIXPublicKey(derBytes []byte) (pub interface{}, err error) ``` > 從該函式的說明:ParsePKIXPublicKey parses a DER encoded public key. These values are typically found in PEM blocks with “BEGIN PUBLIC KEY”。可見這就是解析PublicKey的。另外,這裡說到了PEM,可以上面的encoding/pem對了。 > > 而解析私鑰的,有好幾個方法,從上面的介紹,我們知道,RSA是PKCS#1,剛好有一個方法: ```go func ParsePKCS1PrivateKey(der []byte) (key *rsa.PrivateKey, err error) ``` `返回的就是rsa.PrivateKey` ##### 加密解密實現 `加密` ````go func RsaEncrypt(origData []byte) ([]byte, error) { block, _ := pem.Decode(publicKey) if block == nil { return nil, errors.New("public key error") } pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { return nil, err } pub := pubInterface.(*rsa.PublicKey) return rsa.EncryptPKCS1v15(rand.Reader, pub, origData) } ```` `解密` ```go func RsaDecrypt(ciphertext []byte) ([]byte, error) { block, _ := pem.Decode(privateKey) if block == nil { return nil, errors.New("private key error!") } priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext) } ``` `使用例子` ```go package main import ( "fmt" ) func main() { data, err := RsaEncrypt([]byte("test")) if err != nil { panic(err) } origData, err := RsaDecrypt(data) if err != nil { panic(err) } fmt.Println(string(origData)) } // 此例子是加密完test後立馬解密 ``` `參考:` https://segmentfault.com/a/1190000024557845 https://www.jb51.net/article/89