security - 我使用密码脚本加密的安全性如何? (Golang, AES256, pbkdf2, hmac)

标签 security encryption go aes kdf

首先,我想说这只是一个学习练习,我不打算在生产中使用它。

我用 Golang 写了一个小应用程序,有两个函数:encrypt(plaintext string, password string)decrypt(encrypted string, password string)

加密步骤为:

  1. 随机生成 256 位用作盐
  2. 生成 128 位用作初始化向量
  3. 使用 PDKDF2 从密码和盐生成 32 位 key
  4. 用 key 和明文生成一个32位的HMAC,并将其附加到明文的开头
  5. 使用CFB模式的AES对hmac+明文进行加密

返回的字节数组如下所示:

[256 bit salt] [128 bit iv] encrypted([256 bit hmac] [plaintext])

解密时:

  1. 提取盐并将其与提供的密码一起使用以计算 key
  2. 提取IV并解密密文的加密部分
  3. 从解密后的值中提取mac
  4. 用明文验证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)
}

最佳答案

注意,因为问题是关于加密消息而不是密码:如果你加密小消息而不是散列密码,Go 的 secretbox包——作为其 NaCl 实现的一部分——是可行的方法。如果您打算推出自己的产品——我强烈建议不要这样做,除非它保留在您自己的开发环境中——那么 AES-GCM 是您的不二之选。

否则,以下大部分内容仍然适用:

  1. 对称加密对密码没有用处。您应该没有理由需要返回明文——您应该只关心比较哈希(或者更准确地说,派生 key )。
  2. 与 scrypt 或 bcrypt 相比,PBKDF2 并不理想(10002 轮,2015 年,可能也有点低)。 scrypt 是内存困难的,更难在 GPU 上并行化,并且在 2015 年,它的生命周期足够长,使其比 bcrypt 更安全(如果您的语言的 scrypt 库不是很好,您仍然会使用 bcrypt ).
  3. MAC 然后加密 has issues - 你应该先加密然后再加密 MAC。
  4. 鉴于 #3,您应该使用 AES-GCM(Galois 计数器模式)而不是 AES-CBC + HMAC。

Go 有一个很棒的 bcrypt使用易于使用的 API 打包(为您生成盐;安全地比较)。

我还写了一个scrypt镜像该包的包,因为底层的 scrypt 包需要您验证您自己的参数并生成您自己的盐。

关于security - 我使用密码脚本加密的安全性如何? (Golang, AES256, pbkdf2, hmac),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34552301/

相关文章:

asp.net - 这个新的 ASP.NET 安全漏洞有多严重?我该如何解决它?

java - c# RSA 使用私钥加密

http - 来自 golang 中的 http 请求的表单变量的嵌套值

go - DeleteState 和 PutState 函数不会更改账本状态

passwords - 存储 secret 问题的答案比存储密码更安全吗?

java - Authenticode、SPC 和 Java CodeSign 之间的区别?

algorithm - 密码,盐和安全概念

c++ - 如何阻止加密字符串的反转?

go - 通过 TCP 读取字节并在 Go 中编码为 ISO-8859-9

php - 当我使用加盐的 CRYPT_MD5 来加密我的密码时,加密的是什么?