我的密码脚本加密有多安全? (Golang,AES256,pbkdf2,hmac)

时间:2015-12-31 23:07:27

标签: security encryption go aes kdf

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

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

加密步骤如下:

  1. 生成随机256位以用作盐
  2. 生成128位用作初始化向量
  3. 使用PDKDF2从密码和salt生成32位密钥
  4. 使用密钥和明文生成32位HMAC,并将其附加到明文的开头
  5. 使用AES在CFB模式下加密hmac +明文
  6. 返回的字节数组如下所示:

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

    解密时:

    1. 提取salt并将其与提供的密码一起使用以计算密钥
    2. 提取IV并解密密文的加密部分
    3. 从解密值中提取mac
    4. 使用明文验证mac
    5. 我没有疯狂到在任何生产项目中使用我自己的加密脚本,所以请指向我这样做的任何库(简单的密码/消息加密相对安全)

      这是两个函数的源代码:

      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)
      }
      

1 个答案:

答案 0 :(得分:6)

注意,因为问题是关于加密邮件而不是密码:如果您要加密小邮件而不是哈希密码,请执行secretbox包 - 它的部分NaCl实施 - 是要走的路。如果您打算自己滚动 - 我强烈建议不要这样做,除非它保留在您自己的开发环境中 - 然后AES-GCM就是这里的方式。

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

  1. 对称加密对密码不是很有用。你应该没有理由需要明文 - 你应该只关心比较哈希(或者更确切地说是衍生键)。
  2. 与scrypt或bcrypt相比,PBKDF2并不理想(2015年10002轮,可能也有点低)。 scrypt很难记忆并且难以在GPU上并行化,并且在2015年,它具有足够长的寿命,使其比bcrypt更安全(如果您的语言的scrypt库不是,那么你仍然会使用bcrypt。很棒)。
  3. MAC-then-encrypt has issues - 你应该加密 - 然后MAC。
  4. 鉴于#3,您应该使用AES-GCM(伽罗瓦计数器模式)而非AES-CBC + HMAC。
  5. Go有一个很棒的bcrypt包,其中包含一个易于使用的API(为您生成盐;安全地比较)。

    我还编写了一个反映该软件包的scrypt软件包,因为底层的scrypt软件包要求您验证自己的params并生成自己的salt。