使用 AES 和 Base64 加密字符串
Posted
技术标签:
【中文标题】使用 AES 和 Base64 加密字符串【英文标题】:Encrypting a string with AES and Base64 【发布时间】:2013-09-19 23:20:21 【问题描述】:我正在尝试加密数据库中的一些文本,以便在程序启动期间加载和解密。
我尝试了几种方法,包括第三方库https://github.com/richard-lyman/lithcrypt 均无济于事。使用以下方法加密/解密 8/10 项,但似乎在加密/解密中的某个点留下了一些填充残留物。就目前而言,我的代码是这样的:
package client
import (
"encoding/base64"
"crypto/aes"
"crypto/cipher"
"fmt"
)
var iv = []byte34, 35, 35, 57, 68, 4, 35, 36, 7, 8, 35, 23, 35, 86, 35, 23
func encodeBase64(b []byte) string
return base64.StdEncoding.EncodeToString(b)
func decodeBase64(s string) []byte
data, err := base64.StdEncoding.DecodeString(s)
if err != nil panic(err)
return data
func Encrypt(key, text string) string
block, err := aes.NewCipher([]byte(key))
if err != nil panic(err)
plaintext := []byte(text)
cfb := cipher.NewCFBEncrypter(block, iv)
ciphertext := make([]byte, len(plaintext))
cfb.XORKeyStream(ciphertext, plaintext)
return encodeBase64(ciphertext)
func Decrypt(key, text string) string
block, err := aes.NewCipher([]byte(key))
if err != nil panic(err)
ciphertext := decodeBase64(text)
cfb := cipher.NewCFBEncrypter(block, iv)
plaintext := make([]byte, len(ciphertext))
cfb.XORKeyStream(plaintext, ciphertext)
有人提到我可能需要填充字符串,但我必须填充流密码似乎很奇怪。
下面是这个错误的一个例子:http://play.golang.org/p/4FQBAeHgRs
【问题讨论】:
不确定这是否有帮助,但在您发布的操场示例中,超过 16 字节字符串的任何内容都会出现错误。将密钥设置为 32 字节字符串(而不是 24 字节)可以成功解码您的“plaintext1”字符串。 我希望 IV 是一个常数,仅用于说明目的。 IV 代表初始化向量,这对于每个加密应该是随机的,并且应该保存在密文中。解密时,首先从密文中提取 IV,然后进行通常的解密。来自 Wikipedia (en.wikipedia.org/wiki/…):对于 CBC 和 CFB,重用 IV 会泄露一些关于第一个明文块以及两条消息共享的任何公共前缀的信息。 @Kluyg 非常好的观点。我已修改我的答案以使用推荐的 IV 创建方法。 免责声明:我不是安全专家。 但我开发了这个库,可能会对您有所帮助 github.com/phylake/go-crypto,我强烈推荐 Cryptography Engineering: Design Principles and Practical Applications,它提供了很多清晰性各种流和分组密码模式。 【参考方案1】:这是基于NewCFBEncrypter / NewCFBDecrypter examples 并且似乎可以满足您的要求:
编辑:根据 Kluyg 关于 IV 创建的评论,我已修改示例代码以使用 从密文创建 IV 的推荐方法 与 linked 示例相同的方法来创建 IV从密文中。 (在生产代码中,IV 应该每次单独生成。感谢RoundSparrow hilltx 指出这一点。)
我认为您遇到的问题是由于密钥长度无效,但我不能 100% 确定。
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"io"
"log"
)
func main()
key := []byte("a very very very very secret key") // 32 bytes
plaintext := []byte("some really really really long plaintext")
fmt.Printf("%s\n", plaintext)
ciphertext, err := encrypt(key, plaintext)
if err != nil
log.Fatal(err)
fmt.Printf("%0x\n", ciphertext)
result, err := decrypt(key, ciphertext)
if err != nil
log.Fatal(err)
fmt.Printf("%s\n", result)
// See alternate IV creation from ciphertext below
//var iv = []byte35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05
func encrypt(key, text []byte) ([]byte, error)
block, err := aes.NewCipher(key)
if err != nil
return nil, err
b := base64.StdEncoding.EncodeToString(text)
ciphertext := make([]byte, aes.BlockSize+len(b))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil
return nil, err
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(b))
return ciphertext, nil
func decrypt(key, text []byte) ([]byte, error)
block, err := aes.NewCipher(key)
if err != nil
return nil, err
if len(text) < aes.BlockSize
return nil, errors.New("ciphertext too short")
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
data, err := base64.StdEncoding.DecodeString(string(text))
if err != nil
return nil, err
return data, nil
生产:
一些非常非常长的明文 54618bd6bb10612a7b590c53192df214501e01b685540b012581a0ed9ff3ddaa1f4177cc6186b501fb8cce0c2eb764daff475aab724d4d33e614d7d89cf556d8502fd>92001c
Playground
希望这有助于查明问题。
【讨论】:
感谢您提供如此有力的回答。我已经调整了您的示例以在我的程序中使用。它似乎确实是密钥长度,这很奇怪,因为我原以为它会为无效长度踢出错误,并且/任何/时间也不起作用。 " 我已修改示例代码以使用推荐的方法从密文创建 IV。" - 我不喜欢这样的措辞。 IV 应该与密文一起发送,通常是在前面发送 - 但它们是独立生成的。每次使用密钥时,IV 都必须是唯一的,因此即使加密了相同的字符串“hello”,输出也是唯一的。 @RoundSparrowhilltx 谢谢,我已经更新了答案以指定示例代码使用与链接示例相同的方法。 我只想指出,在加密数据之前对数据进行 base64 编码是没有用的。此外,额外的处理会降低性能,base64 的数据会增加大约 33% 的大小。在原始问题中,加密和解密函数正在处理文本,因此加密数据需要在返回之前进行编码。此处定义的函数处理 []byte,因此没有理由对数据进行 base64 编码。 这没有身份验证 (HMAC),因此不安全。如果您的整个明文都适合内存(如上所示),建议使用 GCM。【参考方案2】:加密很难,go 库可能不够高级,所以很容易出错。
对于任何寻求该领域专家(CoreOS 的安全开发人员)正确执行此操作的示例的人来说,这提供了一个很好的 AES 加密示例(以及加密的其他常见用途)。
https://github.com/gtank/cryptopasta
【讨论】:
加密货币本身并不难,正确使用它却很难。【参考方案3】:这是我刚刚完成的工作演示,它主要使用 go 文档中的代码示例,但它是为执行大多数应用程序(包括我的用例)所期望的加密方法而量身定制的。
它使用 AES 加密。 从字符串加密到base64字符串。易于在 URL 和 dbs 上使用。 从上面创建的base64字符串解密为原始文本。
简单的文本转换无处不在。
GIST: Here is the gist, please let me know if there are any need for the improvements.
这是一个简单的 go 文件,可以运行了。
【讨论】:
【参考方案4】:看来您的操作顺序有点倒退。这是你似乎在做的事情:
ct = encrypt(encode(pt))
pt = decode(decrypt(ct))
它应该看起来更像:
ct = encode(encrypt(pt))
pt = decrypt(decode(ct))
以下对我有用
func Encrypt(key, text []byte) string
block, err := aes.NewCipher(key)
if err != nil
panic(err)
ciphertext := make([]byte, aes.BlockSize+len(text))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(crand.Reader, iv); err != nil
panic(err)
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], text)
return encodeBase64(ciphertext)
func Decrypt(key []byte, b64 string) string
text := decodeBase64(b64)
block, err := aes.NewCipher(key)
if err != nil
panic(err)
if len(text) < aes.BlockSize
panic("ciphertext too short")
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
return string(text)
【讨论】:
【参考方案5】:许多人已经提供了很好的答案。但是正如@PiersyP 在@Intermernet 的答案中指出的那样,没有必要对文本进行base64。所以这里没有base64ing,以防有人赶时间
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"errors"
"fmt"
"io"
"log"
)
func main()
key := []byte("a very very very very secret key") // 32 bytes
plaintext := []byte("some really really really long plaintext")
fmt.Printf("%s\n", plaintext)
ciphertext, err := encrypt(key, plaintext)
if err != nil
log.Fatal(err)
fmt.Printf("%0x\n", ciphertext)
result, err := decrypt(key, ciphertext)
if err != nil
log.Fatal(err)
fmt.Printf("%s\n", result)
// See alternate IV creation from ciphertext below
//var iv = []byte35, 46, 57, 24, 85, 35, 24, 74, 87, 35, 88, 98, 66, 32, 14, 05
func encrypt(key, text []byte) ([]byte, error)
block, err := aes.NewCipher(key)
if err != nil
return nil, err
ciphertext := make([]byte, aes.BlockSize + len(text))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil
return nil, err
cfb := cipher.NewCFBEncrypter(block, iv)
cfb.XORKeyStream(ciphertext[aes.BlockSize:], text)
return ciphertext, nil
func decrypt(key, text []byte) ([]byte, error)
block, err := aes.NewCipher(key)
if err != nil
return nil, err
if len(text) < aes.BlockSize
return nil, errors.New("ciphertext too short")
iv := text[:aes.BlockSize]
text = text[aes.BlockSize:]
cfb := cipher.NewCFBDecrypter(block, iv)
cfb.XORKeyStream(text, text)
return text, nil
【讨论】:
【参考方案6】:实际上并没有回答这个问题。但我在这里为来自搜索引擎的人提供了一个完整的工作示例。
通过添加hash password
从gtank/cryptopasta 扭曲。您可以使用任何密码而不必担心其大小。
Go Playground
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
)
func main()
key := []byte("secret")
ct, err := Encrypt([]byte("Plain text"), key)
if err != nil
panic(err)
fmt.Println("Encrypted:", base64.StdEncoding.EncodeToString(ct))
pt, err := Decrypt(ct, key)
if err != nil
panic(err)
fmt.Println("Decrypted:", string(pt))
// Encrypt encrypts data using 256-bit AES-GCM. This both hides the content of
// the data and provides a check that it hasn't been altered. Output takes the
// form nonce|ciphertext|tag where '|' indicates concatenation.
func Encrypt(plaintext []byte, key []byte) (ciphertext []byte, err error)
k := sha256.Sum256(key)
block, err := aes.NewCipher(k[:])
if err != nil
return nil, err
gcm, err := cipher.NewGCM(block)
if err != nil
return nil, err
nonce := make([]byte, gcm.NonceSize())
_, err = io.ReadFull(rand.Reader, nonce)
if err != nil
return nil, err
return gcm.Seal(nonce, nonce, plaintext, nil), nil
// Decrypt decrypts data using 256-bit AES-GCM. This both hides the content of
// the data and provides a check that it hasn't been altered. Expects input
// form nonce|ciphertext|tag where '|' indicates concatenation.
func Decrypt(ciphertext []byte, key []byte) (plaintext []byte, err error)
k := sha256.Sum256(key)
block, err := aes.NewCipher(k[:])
if err != nil
return nil, err
gcm, err := cipher.NewGCM(block)
if err != nil
return nil, err
if len(ciphertext) < gcm.NonceSize()
return nil, errors.New("malformed ciphertext")
return gcm.Open(nil,
ciphertext[:gcm.NonceSize()],
ciphertext[gcm.NonceSize():],
nil,
)
【讨论】:
以上是关于使用 AES 和 Base64 加密字符串的主要内容,如果未能解决你的问题,请参考以下文章
golang GoLang使用AES加密将字符串加密到base64,反之亦然。