缘起
因为项目中使用 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() 转化
代码参考: 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)
参考文档
golang 中几种加密方式的处理
对称加密算法和分组密码的模式