golang中的加密方式總結
緣起
因為專案中使用mysql的AES_DECRYPT
方法,欲使用golang實現該方法, 但是研究了半天沒明白怎麼回事, 最後才發現golang當前預設支援CBC
的方式,但是mysql當前使用的是ECB
模式, 所以需要使用者分組分塊加密,特總結一下golang中的各個加密演算法
關於密碼學
當前我們專案中常用的加解密的方式無非三種.
對稱加密 , 加解密都使用的是同一個金鑰, 其中的代表就是AES
非對加解密 , 加解密使用不同的金鑰, 其中的代表就是
RSA
簽名演算法, 如MD5
、SHA1
、HMAC
等, 主要用於驗證,防止資訊被修改, 如:檔案校驗、數字簽名、鑑權協議
AES
AES:高階加密標準(Advanced Encryption Standard),又稱Rijndael加密法,這個標準用來替代原先的DES。AES加密資料塊分組長度必須為128bit(byte[16]),金鑰長度可以是128bit(byte[16])、192bit(byte[24])、256bit(byte[32])中的任意一個。
塊:對明文進行加密的時候,先要將明文按照128bit進行劃分。
填充方式:因為明文的長度不一定總是128的整數倍,所以要進行補位,我們這裡採用的是PKCS7填充方式
AES
實現的方式多樣, 其中包括ECB
、CBC
、CFB
、OFB
等
1.電碼本模式 (Electronic Codebook Book (ECB))
將明文分組加密之後的結果直接稱為密文分組。
2.密碼分組連結模式 (Cipher Block Chaining (CBC))
將明文分組與前一個密文分組進行XOR運算,然後再進行加密。每個分組的加解密都依賴於前一個分組。而第一個分組沒有前一個分組,因此需要一個初始化向量
3.計算器模式 (Counter (CTR))
4.密碼反饋模式 (Cipher FeedBack (CFB))
前一個密文分組會被送回到密碼演算法的輸入端。
在CBC和EBC模式中,明文分組都是通過密碼演算法進行加密的。而在CFB模式中,明文分組並沒有通過加密演算法直接進行加密,明文分組和密文分組之間只有一個XOR。
5.輸出反饋模式 (Output FeedBack (OFB))
加密模式 | 對應加解密方法 |
---|---|
CBC | NewCBCDecrypter, NewCBCEncrypter |
CTR | NewCTR |
CFB | NewCFBDecrypter, NewCFBEncrypter |
OFB | NewOFB |
相關示例見:ofollow,noindex" target="_blank">https://golang.org/src/crypto...
1.CBC
模式, 最常見的使用的方式
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)] }
2.ECB
模式: mysql中AES_DECRYPT
函式的實現方式
主要關注三點:
1.呼叫aes.NewCipher([]byte)
是加密關鍵字key的生成方式, 即下面的generateKey
方法
2.分組分塊加密的加密方式
3.mysql中一般需要HEX
函式來轉化資料格式
HEX(AES_ENCRYPT('關鍵資訊', '***—key'))
解密:
AES_DECRYPT(UNHEX('關鍵資訊'), '***-key’)
所以呼叫AESEncrypt
或者AESDecrypt
方法之後, 使用hex.EncodeToString()
轉化
程式碼參考:https://github.com/fkfk/mysql...
package mysqlcrypto import ( "crypto/aes" ) 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 }
CFB
模式
程式碼來源:https://golang.org/src/crypto...
func ExampleNewCFBDecrypter() { // Load your secret key from a safe place and reuse it across multiple // NewCipher calls. (Obviously don't use this example key for anything // real.) If you want to convert a passphrase to a key, use a suitable // package like bcrypt or scrypt. key, _ := hex.DecodeString("6368616e676520746869732070617373") ciphertext, _ := hex.DecodeString("7dd015f06bec7f1b8f6559dad89f4131da62261786845100056b353194ad") block, err := aes.NewCipher(key) if err != nil { panic(err) } // The IV needs to be unique, but not secure. Therefore it's common to // include it at the beginning of the ciphertext. if len(ciphertext) < aes.BlockSize { panic("ciphertext too short") } iv := ciphertext[:aes.BlockSize] ciphertext = ciphertext[aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) // XORKeyStream can work in-place if the two arguments are the same. stream.XORKeyStream(ciphertext, ciphertext) fmt.Printf("%s", ciphertext) // Output: some plaintext } func ExampleNewCFBEncrypter() { // Load your secret key from a safe place and reuse it across multiple // NewCipher calls. (Obviously don't use this example key for anything // real.) If you want to convert a passphrase to a key, use a suitable // package like bcrypt or scrypt. key, _ := hex.DecodeString("6368616e676520746869732070617373") plaintext := []byte("some plaintext") block, err := aes.NewCipher(key) if err != nil { panic(err) } // The IV needs to be unique, but not secure. Therefore it's common to // include it at the beginning of the ciphertext. ciphertext := make([]byte, aes.BlockSize+len(plaintext)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic(err) } stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext) // It's important to remember that ciphertexts must be authenticated // (i.e. by using crypto/hmac) as well as being encrypted in order to // be secure. fmt.Printf("%x\n", ciphertext) }
RSA
首先使用openssl
生成公私鑰
import ( "crypto/rand" "crypto/rsa" "crypto/x509" "encoding/base64" "encoding/pem" "errors" "fmt" ) // 私鑰生成 //openssl genrsa -out rsa_private_key.pem 1024 var privateKey = []byte(` -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDcGsUIIAINHfRTdMmgGwLrjzfMNSrtgIf4EGsNaYwmC1GjF/bM h0Mcm10oLhNrKNYCTTQVGGIxuc5heKd1gOzb7bdTnCDPPZ7oV7p1B9Pud+6zPaco qDz2M24vHFWYY2FbIIJh8fHhKcfXNXOLovdVBE7Zy682X1+R1lRK8D+vmQIDAQAB AoGAeWAZvz1HZExca5k/hpbeqV+0+VtobMgwMs96+U53BpO/VRzl8Cu3CpNyb7HY 64L9YQ+J5QgpPhqkgIO0dMu/0RIXsmhvr2gcxmKObcqT3JQ6S4rjHTln49I2sYTz 7JEH4TcplKjSjHyq5MhHfA+CV2/AB2BO6G8limu7SheXuvECQQDwOpZrZDeTOOBk z1vercawd+J9ll/FZYttnrWYTI1sSF1sNfZ7dUXPyYPQFZ0LQ1bhZGmWBZ6a6wd9 R+PKlmJvAkEA6o32c/WEXxW2zeh18sOO4wqUiBYq3L3hFObhcsUAY8jfykQefW8q yPuuL02jLIajFWd0itjvIrzWnVmoUuXydwJAXGLrvllIVkIlah+lATprkypH3Gyc YFnxCTNkOzIVoXMjGp6WMFylgIfLPZdSUiaPnxby1FNM7987fh7Lp/m12QJAK9iL 2JNtwkSR3p305oOuAz0oFORn8MnB+KFMRaMT9pNHWk0vke0lB1sc7ZTKyvkEJW0o eQgic9DvIYzwDUcU8wJAIkKROzuzLi9AvLnLUrSdI6998lmeYO9x7pwZPukz3era zncjRK3pbVkv0KrKfczuJiRlZ7dUzVO0b6QJr8TRAA== -----END RSA PRIVATE KEY----- `) // 公鑰: 根據私鑰生成 //openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem var publicKey = []byte(` -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDcGsUIIAINHfRTdMmgGwLrjzfM NSrtgIf4EGsNaYwmC1GjF/bMh0Mcm10oLhNrKNYCTTQVGGIxuc5heKd1gOzb7bdT nCDPPZ7oV7p1B9Pud+6zPacoqDz2M24vHFWYY2FbIIJh8fHhKcfXNXOLovdVBE7Z y682X1+R1lRK8D+vmQIDAQAB -----END PUBLIC KEY----- `) // 加密 func RsaEncrypt(origData []byte) ([]byte, error) { //解密pem格式的公鑰 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) } // 解密 func RsaDecrypt(ciphertext []byte) ([]byte, error) { //解密 block, _ := pem.Decode(privateKey) if block == nil { return nil, errors.New("private key error!") } //解析PKCS1格式的私鑰 priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } // 解密 return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext) } func main() { data, _ := RsaEncrypt([]byte("hello world")) fmt.Println(base64.StdEncoding.EncodeToString(data)) origData, _ := RsaDecrypt(data) fmt.Println(string(origData)) }
雜湊演算法
// sha256加密字串 str := "hello world" sum := sha256.Sum256([]byte(str)) fmt.Printf("SHA256:%x\n", sum) // sha256加密檔案內容 func fileSha156() { file, err := os.OpenFile("e:/test.txt", os.O_RDONLY, 0777) if err != nil { panic(err) } defer file.Close() h := sha256.New() // 將檔案內容拷貝到sha256中 io.Copy(h, file) fmt.Printf("%x\n", h.Sum(nil)) } // md5加密 result := md5.Sum([]byte(str)) fmt.Printf("MD5:%x\n", result)