doubaben7394 2015-12-31 23:07
浏览 135
已采纳

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

First, I want to say that this is just an learning exercise and I do not intend to use this in production.

I wrote a small application in Golang with two functions: encrypt(plaintext string, password string) and decrypt(encrypted string, password string)

The encryption steps are:

  1. Generate random 256 bits to use as salt
  2. Generate 128 bits to use as an Initialization Vector
  3. Use PDKDF2 to generate a 32 bit key from the password and salt
  4. Generate an 32 bit HMAC with the key and plaintext, and append it to the beginning of the plaintext
  5. Encrypt the hmac+plaintext with AES in CFB mode

The returned byte array looks like this:

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

When decrypting:

  1. Extract the salt and use it with the provided password to compute the key
  2. Extract the IV and decrypt the encrypted portion of the ciphertext
  3. Extract the mac from the decrypted value
  4. Validate the mac with the plaintext

I'm not crazy enough to use my own encryption script in any production projects, so please point me to any libraries that do this for me (simple password / message encryption that is relatively secure)

Here is the source code to the two functions:

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条回答 默认 最新

  • 普通网友 2015-12-31 23:54
    关注

    Note, since the question was about encrypting messages rather than passwords: If you're encrypting small messages rather than hashing passwords, Go's secretbox package—as part of its NaCl implementation—is the way to go. If you're intent on rolling your own—and I strongly recommend against it, unless it stays within your own dev environment—then AES-GCM is the way to go here.

    Otherwise, most of the below still applies:

    1. Symmetric encryption isn't useful for passwords. There should be no reason why you need the plaintext back—you should only care about comparing hashes (or, more precisely, derivative keys).
    2. PBKDF2, compared to scrypt or bcrypt, is not ideal (10002 rounds, in 2015, is probably a bit low too). scrypt is memory-hard and much harder to parallelize on a GPU, and in 2015, has had a sufficiently long life as to make it safer than bcrypt (you would still use bcrypt in cases where the scrypt library for your language wasn't great).
    3. MAC-then-encrypt has issues - you should encrypt-then-MAC.
    4. Given #3, you should use AES-GCM (Galois Counter Mode) over AES-CBC + HMAC.

    Go has a great bcrypt package with an easy-to-use API (generates salts for you; securely compares).

    I also wrote an scrypt package that mirrors that package, as the underlying scrypt package requires you to validate your own params and generate your own salts.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥100 c语言,请帮蒟蒻看一个题
  • ¥15 名为“Product”的列已属于此 DataTable
  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站
  • ¥50 成都蓉城足球俱乐部小程序抢票
  • ¥15 yolov7训练自己的数据集
  • ¥15 esp8266与51单片机连接问题(标签-单片机|关键词-串口)(相关搜索:51单片机|单片机|测试代码)