feat: 新增XTS/CCM流式与KDF能力,补充安全测试并更新README/CHANGELOG

This commit is contained in:
2026-03-18 13:43:18 +08:00
parent e89350b56a
commit 4fa79744e8
44 changed files with 4636 additions and 77 deletions
+50
View File
@@ -1,6 +1,7 @@
package hashx
import (
"bytes"
"crypto/md5"
"encoding/hex"
"os"
@@ -87,3 +88,52 @@ func TestFileSumUnsupportedMethod(t *testing.T) {
t.Fatalf("expected unsupported method error")
}
}
func TestPBKDF2SHA256Vector(t *testing.T) {
got, err := DerivePBKDF2SHA256Key("password", []byte("salt"), 1, 32)
if err != nil {
t.Fatalf("DerivePBKDF2SHA256Key failed: %v", err)
}
const want = "120fb6cffcf8b32c43e7225256c4f837a86548c92ccc35480805987cb70be17b"
if hex.EncodeToString(got) != want {
t.Fatalf("pbkdf2-sha256 vector mismatch: got %x want %s", got, want)
}
}
func TestPBKDF2AndArgon2Deterministic(t *testing.T) {
a, err := DerivePBKDF2SHA512Key("password", []byte("salt"), 1000, 32)
if err != nil {
t.Fatalf("DerivePBKDF2SHA512Key failed: %v", err)
}
b, err := DerivePBKDF2SHA512Key("password", []byte("salt"), 1000, 32)
if err != nil {
t.Fatalf("DerivePBKDF2SHA512Key failed: %v", err)
}
if !bytes.Equal(a, b) {
t.Fatalf("pbkdf2-sha512 must be deterministic")
}
params := Argon2Params{Time: 1, Memory: 32 * 1024, Threads: 1, KeyLen: 32}
argA, err := DeriveArgon2idKey("password", []byte("salt-salt"), params)
if err != nil {
t.Fatalf("DeriveArgon2idKey failed: %v", err)
}
argB, err := DeriveArgon2idKey("password", []byte("salt-salt"), params)
if err != nil {
t.Fatalf("DeriveArgon2idKey failed: %v", err)
}
if !bytes.Equal(argA, argB) {
t.Fatalf("argon2id must be deterministic")
}
}
func TestKDFInvalidParams(t *testing.T) {
if _, err := DerivePBKDF2SHA256Key("password", nil, 1, 32); err == nil {
t.Fatalf("expected pbkdf2 salt error")
}
if _, err := DerivePBKDF2SHA256Key("password", []byte("salt"), 0, 32); err == nil {
t.Fatalf("expected pbkdf2 iterations error")
}
if _, err := DeriveArgon2idKey("password", []byte("salt"), Argon2Params{}); err == nil {
t.Fatalf("expected argon2 params error")
}
}
+90
View File
@@ -0,0 +1,90 @@
package hashx
import (
"crypto/pbkdf2"
"crypto/sha256"
"crypto/sha512"
"errors"
"golang.org/x/crypto/argon2"
)
var (
ErrInvalidKDFSalt = errors.New("kdf salt must be non-empty")
ErrInvalidKDFIterations = errors.New("kdf iterations must be > 0")
ErrInvalidKDFKeyLength = errors.New("kdf key length must be > 0")
ErrInvalidArgon2Params = errors.New("argon2 params must have time, memory, threads, and key length > 0")
)
// Argon2Params configures Argon2 key derivation.
type Argon2Params struct {
Time uint32
Memory uint32
Threads uint8
KeyLen uint32
}
// DefaultArgon2idParams returns a conservative default suitable for general online usage.
func DefaultArgon2idParams() Argon2Params {
return Argon2Params{
Time: 1,
Memory: 64 * 1024, // 64 MiB in KiB
Threads: 4,
KeyLen: 32,
}
}
func validatePBKDF2Params(salt []byte, iterations, keyLen int) error {
if len(salt) == 0 {
return ErrInvalidKDFSalt
}
if iterations <= 0 {
return ErrInvalidKDFIterations
}
if keyLen <= 0 {
return ErrInvalidKDFKeyLength
}
return nil
}
func validateArgon2Params(salt []byte, params Argon2Params) error {
if len(salt) == 0 {
return ErrInvalidKDFSalt
}
if params.Time == 0 || params.Memory == 0 || params.Threads == 0 || params.KeyLen == 0 {
return ErrInvalidArgon2Params
}
return nil
}
// DerivePBKDF2SHA256Key derives a key with PBKDF2-HMAC-SHA256.
func DerivePBKDF2SHA256Key(password string, salt []byte, iterations, keyLen int) ([]byte, error) {
if err := validatePBKDF2Params(salt, iterations, keyLen); err != nil {
return nil, err
}
return pbkdf2.Key(sha256.New, password, salt, iterations, keyLen)
}
// DerivePBKDF2SHA512Key derives a key with PBKDF2-HMAC-SHA512.
func DerivePBKDF2SHA512Key(password string, salt []byte, iterations, keyLen int) ([]byte, error) {
if err := validatePBKDF2Params(salt, iterations, keyLen); err != nil {
return nil, err
}
return pbkdf2.Key(sha512.New, password, salt, iterations, keyLen)
}
// DeriveArgon2idKey derives a key with Argon2id.
func DeriveArgon2idKey(password string, salt []byte, params Argon2Params) ([]byte, error) {
if err := validateArgon2Params(salt, params); err != nil {
return nil, err
}
return argon2.IDKey([]byte(password), salt, params.Time, params.Memory, params.Threads, params.KeyLen), nil
}
// DeriveArgon2iKey derives a key with Argon2i.
func DeriveArgon2iKey(password string, salt []byte, params Argon2Params) ([]byte, error) {
if err := validateArgon2Params(salt, params); err != nil {
return nil, err
}
return argon2.Key([]byte(password), salt, params.Time, params.Memory, params.Threads, params.KeyLen), nil
}