Golang代码审查请求:PBKDF2/AES256 CTR模式实现

Golang代码审查请求:PBKDF2/AES256 CTR模式实现 我正在学习密码学,并选择 Golang 作为实现语言。我编写了一个简单的工具来验证我对 AES256 和 CTR 模式的理解。非常希望有密码学实际经验的人能帮忙审查我的代码。请在此处查看代码:GoCrypt

代码如下:

package crypt

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	b64 "encoding/base64"
	"errors"

	"golang.org/x/crypto/pbkdf2"
)

//hashPassword generates a 32 byte key for use in both AES256 encryption and HMAC_SHA256 mac generation
func hashPassword(password, salt []byte) []byte {
	return pbkdf2.Key(password, salt, 10000, 32, sha256.New)
}

//VerifyHMAC compares MACs for validity in order to avoid timing side-channels.Generates the second ciphertext's MAC using the same key that generated the first ciphertext's MAC
func verifyHMAC256(ciphertext, ciphertextMAC, key []byte) bool {
	mac := hmac.New(sha256.New, key)
	mac.Write(ciphertext)
	expectedMAC := mac.Sum(nil)
	return hmac.Equal(ciphertextMAC, expectedMAC)
}

//genHMAC256 generates a hash of the encrypted text
func genHMAC256(ciphertext, key []byte) []byte {
	mac := hmac.New(sha256.New, key)
	mac.Write(ciphertext)
	hmac := mac.Sum(nil)
	return hmac
}

//Encrypt encrypts using the key from Hashpassword() then
//generates the mac of the encrypted text using GenHmac256
//and then appends the ciphertext to its mac
func Encrypt(text string, passphrase string) string {

	//It is recommended that your salt be at least 8 bytes long
	salt := make([]byte, 8)
	_, err := rand.Read(salt)

	if err != nil {
		return err.Error()
	}

	key := hashPassword([]byte(passphrase), salt)
	block, err := aes.NewCipher(key)

	if err != nil {
		return err.Error()
	}

	plaintext := []byte(text)

	ciphertext := make([]byte, aes.BlockSize+len(plaintext))

	iv := ciphertext[:aes.BlockSize]
	_, err = rand.Read(iv)
	if err != nil {
		return err.Error()
	}

	stream := cipher.NewCTR(block, iv)
	stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

	hmac := genHMAC256(ciphertext, key)

	ciphertext = append(hmac, ciphertext...)

	return b64.StdEncoding.EncodeToString([]byte("Gocrypt_" + string(salt) + string(ciphertext)))
}

//Decrypt obtains the mac from the first 32 bytes of the ciphertext
//then checks whether its valid verifyHMAC().If it is valid it obtains
//the initialisation vector(16 bytes) also known as the nonce from the
//second batch of 32 bytes from the last byte position of the mac.It
//then obtains the ciphertext payload from the remaining slice of bytes:
//			ciphertext[48:].
//It is this ciphertext payload that is now XOR'd back to plaintext
func Decrypt(encrypted string, passphrase string) string {
	ct, err := b64.StdEncoding.DecodeString(encrypted)

	if err != nil {
		return err.Error()
	}

	if string(ct[:8]) != "Gocrypt_" {
		return ""
	}

	salt := ct[8:16]
	ct = ct[16:]

	key := hashPassword([]byte(passphrase), salt)

	block, err := aes.NewCipher(key)

	if err != nil {
		return err.Error()
	}

	hmac := ct[0:32]

	if ok := verifyHMAC256(ct[32:], hmac, key); ok {
		//length of the mac = 32 bytes
		//length of the aes block = 16 bytes
		//iv = d.ciphertext(32:48) = 16 bytes
		iv := ct[len(hmac) : len(hmac)+aes.BlockSize]

		//len(d.ciphertext) = len(mac) + len(iv) + len(ciphertext_payload)
		plaintext := make([]byte, len(ct)-(len(hmac)+aes.BlockSize))

		stream := cipher.NewCTR(block, iv)

		//len(hmac)+aes.BlockSize = 48
		stream.XORKeyStream(plaintext, ct[len(hmac)+aes.BlockSize:])

		return string(plaintext)
	}
	hmacError := errors.New("Invalid hmac")

	return hmacError.Error()

}

更多关于Golang代码审查请求:PBKDF2/AES256 CTR模式实现的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于Golang代码审查请求:PBKDF2/AES256 CTR模式实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


以下是针对您提供的Go语言PBKDF2/AES256 CTR模式实现的代码审查意见。我将从密码学安全性、代码结构和潜在问题等方面进行分析,并提供示例代码说明。

密码学实现问题

1. 密钥派生和HMAC密钥复用

当前实现使用同一个PBKDF2派生密钥用于AES加密和HMAC验证,这违反了密码学最佳实践:

// 问题:同一密钥用于加密和认证
key := hashPassword([]byte(passphrase), salt)

// 建议:为加密和认证分别派生密钥
func deriveKeys(password, salt []byte) (encKey, authKey []byte) {
    // 派生64字节,前32字节用于加密,后32字节用于认证
    keyMaterial := pbkdf2.Key(password, salt, 10000, 64, sha256.New)
    return keyMaterial[:32], keyMaterial[32:]
}

2. 数据格式和解析问题

当前的数据格式解析存在边界条件风险:

// 当前实现的问题代码
if string(ct[:8]) != "Gocrypt_" {
    return ""
}
salt := ct[8:16]
ct = ct[16:]

// 改进方案:使用明确的长度常量
const (
    headerLen = 8  // "Gocrypt_"
    saltLen   = 8
    hmacLen   = 32
    ivLen     = aes.BlockSize
)

func parseEncryptedData(ct []byte) (salt, hmac, iv, ciphertext []byte, err error) {
    if len(ct) < headerLen+saltLen+hmacLen+ivLen {
        return nil, nil, nil, nil, errors.New("insufficient data")
    }
    
    if string(ct[:headerLen]) != "Gocrypt_" {
        return nil, nil, nil, nil, errors.New("invalid header")
    }
    
    salt = ct[headerLen : headerLen+saltLen]
    hmac = ct[headerLen+saltLen : headerLen+saltLen+hmacLen]
    iv = ct[headerLen+saltLen+hmacLen : headerLen+saltLen+hmacLen+ivLen]
    ciphertext = ct[headerLen+saltLen+hmacLen+ivLen:]
    
    return salt, hmac, iv, ciphertext, nil
}

3. 错误处理不一致

加密函数返回错误字符串,解密函数也返回字符串,这不符合Go语言的错误处理惯例:

// 当前问题:混合返回类型
func Encrypt(text string, passphrase string) string {
    // 错误时返回错误字符串
    if err != nil {
        return err.Error()  // 问题:调用方无法区分成功和错误
    }
}

// 改进方案:使用标准错误返回
func Encrypt(text string, passphrase string) (string, error) {
    salt := make([]byte, 8)
    if _, err := rand.Read(salt); err != nil {
        return "", fmt.Errorf("failed to generate salt: %w", err)
    }
    
    // ... 其余代码
    
    return b64.StdEncoding.EncodeToString(encodedData), nil
}

4. CTR模式的安全考虑

CTR模式需要确保IV不会重复使用:

// 当前IV生成是安全的,但应考虑添加长度检查
iv := ciphertext[:aes.BlockSize]
if _, err := rand.Read(iv); err != nil {
    return "", fmt.Errorf("failed to generate IV: %w", err)
}

// 确保IV长度正确
if len(iv) != aes.BlockSize {
    return "", errors.New("invalid IV length")
}

5. HMAC验证时机

当前实现在解密时先解析数据再验证HMAC,这存在潜在风险:

// 改进的HMAC验证流程
func Decrypt(encrypted string, passphrase string) (string, error) {
    ct, err := b64.StdEncoding.DecodeString(encrypted)
    if err != nil {
        return "", fmt.Errorf("base64 decode failed: %w", err)
    }
    
    salt, receivedHMAC, iv, ciphertext, err := parseEncryptedData(ct)
    if err != nil {
        return "", fmt.Errorf("data parsing failed: %w", err)
    }
    
    encKey, authKey := deriveKeys([]byte(passphrase), salt)
    
    // 先验证HMAC再继续处理
    if !verifyHMAC256(append(iv, ciphertext...), receivedHMAC, authKey) {
        return "", errors.New("HMAC verification failed")
    }
    
    // HMAC验证通过后才进行解密
    block, err := aes.NewCipher(encKey)
    if err != nil {
        return "", fmt.Errorf("cipher creation failed: %w", err)
    }
    
    stream := cipher.NewCTR(block, iv)
    plaintext := make([]byte, len(ciphertext))
    stream.XORKeyStream(plaintext, ciphertext)
    
    return string(plaintext), nil
}

性能和安全改进

使用常量时间比较

当前已经正确使用了hmac.Equal,这是很好的实践:

// 当前实现正确使用了常量时间比较
return hmac.Equal(ciphertextMAC, expectedMAC)

增加迭代次数

考虑增加PBKDF2迭代次数以提高安全性:

const (
    pbkdf2Iterations = 100000  // 从10000增加到100000
    saltLength       = 16      // 从8增加到16字节
)

func hashPassword(password, salt []byte) []byte {
    return pbkdf2.Key(password, salt, pbkdf2Iterations, 32, sha256.New)
}

这些修改将提高代码的安全性、健壮性和可维护性,同时保持原有的加密功能。

回到顶部