首先,我想说这只是一个学习练习,我不打算在生产中使用它.
我在Golang中编写了一个带有两个函数的小应用程序:encrypt(plaintext string, password string)
和decrypt(encrypted string, password string)
加密步骤是:
生成随机256位以用作盐
生成128位以用作初始化向量
使用PDKDF2从密码和salt生成32位密钥
使用密钥和明文生成32位HMAC,并将其附加到明文的开头
在CFB模式下使用AES加密hmac + plaintext
返回的字节数组如下所示:
[256 bit salt] [128 bit iv] encrypted([256 bit hmac] [plaintext])
解密时:
提取salt并使用提供的密码来计算密钥
提取IV并解密密文的加密部分
从解密的值中提取mac
用明文验证mac
我没有疯狂到在任何生产项目中使用我自己的加密脚本,所以请指向我这样做的任何库(简单的密码/消息加密相对安全)
这是两个函数的源代码:
package main import ( "io" "crypto/rand" "crypto/cipher" "crypto/aes" "crypto/sha256" "crypto/hmac" "golang.org/x/crypto/pbkdf2" ) const saltlen = 32 const keylen = 32 const iterations = 100002 // returns ciphertext of the following format: // [32 bit salt][128 bit iv][encrypted plaintext] func encrypt(plaintext string, password string) string { // allocate memory to hold the header of the ciphertext header := make([]byte, saltlen + aes.BlockSize) // generate salt salt := header[:saltlen] if _, err := io.ReadFull(rand.Reader, salt); err != nil { panic(err) } // generate initialization vector iv := header[saltlen:aes.BlockSize+saltlen] if _, err := io.ReadFull(rand.Reader, iv); err != nil { panic(err) } // generate a 32 bit key with the provided password key := pbkdf2.Key([]byte(password), salt, iterations, keylen, sha256.New) // generate a hmac for the message with the key mac := hmac.New(sha256.New, key) mac.Write([]byte(plaintext)) hmac := mac.Sum(nil) // append this hmac to the plaintext plaintext = string(hmac) + plaintext //create the cipher block, err := aes.NewCipher(key) if err != nil { panic(err) } // allocate space for the ciphertext and write the header to it ciphertext := make([]byte, len(header) + len(plaintext)) copy(ciphertext, header) // encrypt stream := cipher.NewCFBEncrypter(block, iv) stream.XORKeyStream(ciphertext[aes.BlockSize+saltlen:], []byte(plaintext)) return string(ciphertext) } func decrypt(encrypted string, password string) string { ciphertext := []byte(encrypted) // get the salt from the ciphertext salt := ciphertext[:saltlen] // get the IV from the ciphertext iv := ciphertext[saltlen:aes.BlockSize+saltlen] // generate the key with the KDF key := pbkdf2.Key([]byte(password), salt, iterations, keylen, sha256.New) block, err := aes.NewCipher(key) if (err != nil) { panic(err) } if len(ciphertext) < aes.BlockSize { return "" } decrypted := ciphertext[saltlen+aes.BlockSize:] stream := cipher.NewCFBDecrypter(block, iv) stream.XORKeyStream(decrypted, decrypted) // extract hmac from plaintext extractedMac := decrypted[:32] plaintext := decrypted[32:] // validate the hmac mac := hmac.New(sha256.New, key) mac.Write(plaintext) expectedMac := mac.Sum(nil) if !hmac.Equal(extractedMac, expectedMac) { return "" } return string(plaintext) }
elithrar.. 7
注意,因为问题是关于加密消息而不是密码:如果你正在加密小消息而不是加密密码,Go的secretbox软件包 - 作为其NaCl实现的一部分 - 是要走的路.如果您打算自己滚动 - 我强烈建议不要这样做,除非它保持在您自己的开发环境中 - 然后AES-GCM就是这里的方式.
否则,以下大部分仍然适用:
对称加密对密码无用.你应该没有理由需要明文 - 你应该只关心比较哈希(或者更确切地说,衍生键).
与scrypt或bcrypt相比,PBKDF2并不理想(在2015年,10002轮,可能也有点低).scrypt难以记忆并且难以在GPU上并行化,并且在2015年,它具有足够长的寿命以使其比bcrypt更安全(在您的语言的scrypt库不是很好的情况下,您仍然会使用bcrypt ).
MAC-then-encrypt 有问题 - 你应该加密 - 然后MAC.
鉴于#3,您应该使用AES-GCM(伽罗瓦计数器模式)而不是AES-CBC + HMAC.
Go有一个很棒的bcrypt软件包和一个易于使用的API(为您生成盐;安全地比较).
我还写了一个反映该包的scrypt包,因为底层的scrypt包需要你验证你自己的params并生成你自己的盐.
注意,因为问题是关于加密消息而不是密码:如果你正在加密小消息而不是加密密码,Go的secretbox软件包 - 作为其NaCl实现的一部分 - 是要走的路.如果您打算自己滚动 - 我强烈建议不要这样做,除非它保持在您自己的开发环境中 - 然后AES-GCM就是这里的方式.
否则,以下大部分仍然适用:
对称加密对密码无用.你应该没有理由需要明文 - 你应该只关心比较哈希(或者更确切地说,衍生键).
与scrypt或bcrypt相比,PBKDF2并不理想(在2015年,10002轮,可能也有点低).scrypt难以记忆并且难以在GPU上并行化,并且在2015年,它具有足够长的寿命以使其比bcrypt更安全(在您的语言的scrypt库不是很好的情况下,您仍然会使用bcrypt ).
MAC-then-encrypt 有问题 - 你应该加密 - 然后MAC.
鉴于#3,您应该使用AES-GCM(伽罗瓦计数器模式)而不是AES-CBC + HMAC.
Go有一个很棒的bcrypt软件包和一个易于使用的API(为您生成盐;安全地比较).
我还写了一个反映该包的scrypt包,因为底层的scrypt包需要你验证你自己的params并生成你自己的盐.