github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/cry/aes.go (about)

     1  package cry
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"crypto/rand"
     7  	"encoding/hex"
     8  	"fmt"
     9  	"io"
    10  
    11  	"github.com/pkg/errors"
    12  )
    13  
    14  // AESOpt contains all aes session option
    15  type AESOpt struct {
    16  	aesGCM cipher.AEAD
    17  }
    18  
    19  // NewAESOpt is function to create new configuration of aes algorithm option
    20  // the secret must be hexa a-f & 0-9
    21  func NewAESOpt(secret string) (*AESOpt, error) {
    22  	if len(secret) != 64 {
    23  		return nil, errors.New("Secret must be 64 character")
    24  	}
    25  	key, err := hex.DecodeString(secret)
    26  	if err != nil {
    27  		return nil, errors.Wrap(err, "NewAESOpt.hex.DecodeString")
    28  	}
    29  
    30  	// Create a new Cipher Block from the key
    31  	block, err := aes.NewCipher(key)
    32  	if err != nil {
    33  		return nil, errors.Wrap(err, "NewAESOpt.aes.NewCipher")
    34  	}
    35  
    36  	// AES本身有四种可能的模式,但是常用的只有两种:CBC和GCM。
    37  	// CBC模式就是一个一个块的计算每个块的编码结果,下一个块输入上一个块的计算结果,第一个块输入的是IV(初始向量)。如此所有的数据块形成一条链,逐个计算得到了最终的加密数据。
    38  	// GCM来自于AES的CTR模式,CTR是指计数器模式。GCM是利用GMAC(基于伽罗华域的MAC)和AES的CTR模式的组合。GMAC比普通的MAC算法快(毕竟冠以伽罗华之名),
    39  	// GCM模式与CBC的一个最大的区别是GCM模式不再把上一个数据块的计算结果输入到下一个数据块的计算,而是在分好的数据块中任意位置开始计算,
    40  	// 由一个计数器和一个不变的IV值(nounce)来控制每一次计算的随机性。由于下一次的计算并不依赖于上一次的结果,所以GCM模式可以实现大规模的并行化,并且Intel还专门推出了clmul指令用于加速GCM的运算速度,可见其应用之广。
    41  	// 推荐先后顺序:GCM > CTR > CBC http://nanhuacoder.top/2018/01/03/iOS-UseAES
    42  	// Create a new GCM - https://en.wikipedia.org/wiki/Galois/Counter_Mode
    43  	// https://golang.org/pkg/crypto/cipher/#NewGCM
    44  	aesGCM, err := cipher.NewGCM(block)
    45  	if err != nil {
    46  		return nil, errors.Wrap(err, "NewAESOpt.cipher.NewGCM")
    47  	}
    48  
    49  	return &AESOpt{
    50  		aesGCM: aesGCM,
    51  	}, nil
    52  }
    53  
    54  // Encrypt is function to encrypt data using AES algorithm
    55  func (aesOpt *AESOpt) Encrypt(plainText []byte) (string, error) {
    56  	// Create a nonce. Nonce should be from GCM
    57  	nonce := make([]byte, aesOpt.aesGCM.NonceSize())
    58  	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
    59  		return "", errors.Wrap(err, "encryptAES.io.ReadFull")
    60  	}
    61  
    62  	// Encrypt the data using aesGCM.Seal
    63  	// Since we don't want to save the nonce somewhere else in this case, we add it as a prefix to the encrypted data. The first nonce argument in Seal is the prefix.
    64  	ciphertext := aesOpt.aesGCM.Seal(nonce, nonce, plainText, nil)
    65  	return fmt.Sprintf("%x", ciphertext), nil
    66  }
    67  
    68  // Decrypt is function to decypt data using AES algorithm
    69  func (aesOpt *AESOpt) Decrypt(chiperText []byte) (string, error) {
    70  	enc, _ := hex.DecodeString(string(chiperText))
    71  
    72  	// Get the nonce size
    73  	nonceSize := aesOpt.aesGCM.NonceSize()
    74  	if len(enc) < nonceSize {
    75  		return "", errors.New("The data can't be decrypted")
    76  	}
    77  	// Extract the nonce from the encrypted data
    78  	nonce, ciphertext := enc[:nonceSize], enc[nonceSize:]
    79  
    80  	// Decrypt the data
    81  	plainText, err := aesOpt.aesGCM.Open(nil, nonce, ciphertext, nil)
    82  	if err != nil {
    83  		return "", errors.Wrap(err, "decryptAES.aesGCM.Open")
    84  	}
    85  
    86  	return fmt.Sprintf("%s", plainText), nil
    87  }