了解如何使用Go加密和解密数据。 请记住,这不是一门关于密码学的课程,而是一门用Go语言实现的课程。
使用AES GCM进行加密和解密
你有一个文件和一个密码,并且想要使用密码对文件进行加密。
有很多加密算法。
本章介绍如何在GCM模式下使用对称算法AES(高级加密标准)。
GCM模式同时提供加密和身份验证。
未经身份验证,攻击者可能会更改加密字节,这将导致解密成功但数据损坏。 通过添加身份验证,GCM模式可以检测到加密数据已损坏。
对称意味着我们可以使用相同的密码来加密和解密数据。
AES使用16个字节的密钥作为密码。 人类喜欢任意长度的密码。
为了支持人类,我们需要从人类密码派生AES密钥。 这比看起来要难,因此应该使用经过充分研究并被认为是加密安全的方法之一。 这些方法之一是scrypt密钥派生功能。
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"golang.org/x/crypto/scrypt"
)
func aesKeyFromPassword(password string) ([]byte, error) {
// DO NOT use this salt value; generate your own random salt. 8 bytes is
// a good length. Keep the salt secret.
secretSalt := []byte{0xbc, 0x1e, 0x07, 0xd7, 0xb2, 0xa2, 0x5e, 0x2c}
return scrypt.Key([]byte(password), secretSalt, 32768, 8, 1, 32)
}
func aesGcmEncrypt(unencrypted []byte, password string) ([]byte, error) {
key, err := aesKeyFromPassword(password)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// generate a random nonce (makes encryption stronger)
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
encrypted := gcm.Seal(nil, nonce, unencrypted, nil)
// we need nonce for decryption so we put it at the beginning
// of encrypted text
return append(nonce, encrypted...), nil
}
func aesGcmDecrypt(encrypted []byte, password string) ([]byte, error) {
key, err := aesKeyFromPassword(password)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
if len(encrypted) < gcm.NonceSize() {
return nil, errors.New("Invalid data")
}
// extract random nonce we added to the beginning of the file
nonce := encrypted[:gcm.NonceSize()]
encrypted = encrypted[gcm.NonceSize():]
return gcm.Open(nil, nonce, encrypted, nil)
}
func main() {
password := "my password"
d, err := ioutil.ReadFile("main.go")
if err != nil {
log.Fatalf("ioutil.ReadFile() failed with %s\n", err)
}
encrypted, err := aesGcmEncrypt(d, password)
if err != nil {
log.Fatalf("aesGcmEncrypt() failed with %s\n", err)
}
decrypted, err := aesGcmDecrypt(encrypted, password)
if err != nil {
log.Fatalf("aesGcmDecrypt() failed with %s\n", err)
}
if !bytes.Equal(d, decrypted) {
log.Fatalf("decryption created data different than original\n")
} else {
fmt.Printf("Encryption in decryption worked!\n")
}
}
加密是一个棘手的主题,犯一个错误就会使攻击者破坏加密并解密文件。
将人可读的密码转换为随机加密密钥非常重要。
人倾向于只使用可能的字节子集作为密码,这使得它们更容易破解。
Scrypt被认为是一种通过人工密码生成加密密钥的好算法。 可见,它还使用了一个盐值,你应该对其保密。
AES算法有多种变体。 我们之所以选择GCM,是因为它结合了身份验证和加密功能。 身份验证检测加密数据的修改。
为了使加密更强,GCM模式需要额外的随机字节。 我们选择为每个文件生成唯一的随机数,并将其存储在加密数据的开头(随机数不必是秘密的)。
一种替代方法是仅生成一个随机数并将其用于所有文件。