golang中的加密方式總結

緣起

由於項目中使用mysql的AES_DECRYPT方法,欲使用golang實現該方法, 可是研究了半天沒明白怎麼回事, 最後才發現golang當前默認支持CBC的方式,可是mysql當前使用的是ECB模式, 因此須要使用者分組分塊加密,特總結一下golang中的各個加密算法mysql

關於密碼學

當前咱們項目中經常使用的加解密的方式無非三種.
對稱加密, 加解密都使用的是同一個密鑰, 其中的表明就是AES
非對加解密, 加解密使用不一樣的密鑰, 其中的表明就是RSA
簽名算法, 如MD5SHA1HMAC等, 主要用於驗證,防止信息被修改, 如:文件校驗、數字簽名、鑑權協議git

AES

AES:高級加密標準(Advanced Encryption Standard),又稱Rijndael加密法,這個標準用來替代原先的DES。AES加密數據塊分組長度必須爲128bit(byte[16]),密鑰長度能夠是128bit(byte[16])、192bit(byte[24])、256bit(byte[32])中的任意一個。github

塊:對明文進行加密的時候,先要將明文按照128bit進行劃分。golang

填充方式:由於明文的長度不必定老是128的整數倍,因此要進行補位,咱們這裏採用的是PKCS7填充方式算法

AES實現的方式多樣, 其中包括ECBCBCCFBOFBsql

1.電碼本模式(Electronic Codebook Book (ECB))
將明文分組加密以後的結果直接稱爲密文分組。segmentfault

2.密碼分組連接模式(Cipher Block Chaining (CBC))
將明文分組與前一個密文分組進行XOR運算,而後再進行加密。每一個分組的加解密都依賴於前一個分組。而第一個分組沒有前一個分組,所以須要一個初始化向量數組

3.計算器模式(Counter (CTR))app

4.密碼反饋模式(Cipher FeedBack (CFB))
前一個密文分組會被送回到密碼算法的輸入端。
在CBC和EBC模式中,明文分組都是經過密碼算法進行加密的。而在CFB模式中,明文分組並無經過加密算法直接進行加密,明文分組和密文分組之間只有一個XOR。函數

5.輸出反饋模式(Output FeedBack (OFB))

加密模式 對應加解密方法
CBC NewCBCDecrypter, NewCBCEncrypter
CTR NewCTR
CFB NewCFBDecrypter, NewCFBEncrypter
OFB NewOFB

相關示例見: 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)

參考文檔

相關文章
相關標籤/搜索