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 |
相关示例见: 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()
转化
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)
参考文档
- golang中几种加密方式的处理
- 对称加密算法和分组密码的模式