feat(trix): add Argon2id key derivation alongside legacy SHA-256
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
23a3d2fe57
commit
220a3458d7
4 changed files with 141 additions and 12 deletions
8
go.mod
8
go.mod
|
|
@ -13,8 +13,9 @@ require (
|
|||
github.com/spf13/cobra v1.10.1
|
||||
github.com/ulikunitz/xz v0.5.15
|
||||
github.com/wailsapp/wails/v2 v2.11.0
|
||||
golang.org/x/mod v0.30.0
|
||||
golang.org/x/net v0.47.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/mod v0.32.0
|
||||
golang.org/x/net v0.49.0
|
||||
golang.org/x/oauth2 v0.33.0
|
||||
)
|
||||
|
||||
|
|
@ -60,9 +61,8 @@ require (
|
|||
github.com/wailsapp/go-webview2 v1.0.22 // indirect
|
||||
github.com/wailsapp/mimetype v1.4.1 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
|
|
|||
16
go.sum
16
go.sum
|
|
@ -155,18 +155,18 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI
|
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo=
|
||||
golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
||||
|
|
@ -190,8 +190,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@ package trix
|
|||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
|
||||
"github.com/Snider/Borg/pkg/datanode"
|
||||
"github.com/Snider/Enchantrix/pkg/crypt"
|
||||
"github.com/Snider/Enchantrix/pkg/enchantrix"
|
||||
|
|
@ -61,11 +64,53 @@ func FromTrix(data []byte, password string) (*datanode.DataNode, error) {
|
|||
|
||||
// DeriveKey derives a 32-byte key from a password using SHA-256.
|
||||
// This is used for ChaCha20-Poly1305 encryption which requires a 32-byte key.
|
||||
// Deprecated: Use DeriveKeyArgon2 for new code; this remains for backward compatibility.
|
||||
func DeriveKey(password string) []byte {
|
||||
hash := sha256.Sum256([]byte(password))
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
// Argon2Params holds the tunable parameters for Argon2id key derivation.
|
||||
type Argon2Params struct {
|
||||
Time uint32
|
||||
Memory uint32 // in KiB
|
||||
Threads uint32
|
||||
}
|
||||
|
||||
// DefaultArgon2Params returns sensible default parameters for Argon2id.
|
||||
func DefaultArgon2Params() Argon2Params {
|
||||
return Argon2Params{
|
||||
Time: 3,
|
||||
Memory: 64 * 1024,
|
||||
Threads: 4,
|
||||
}
|
||||
}
|
||||
|
||||
// Encode serialises the Argon2Params as 12 bytes (3 x uint32 little-endian).
|
||||
func (p Argon2Params) Encode() []byte {
|
||||
buf := make([]byte, 12)
|
||||
binary.LittleEndian.PutUint32(buf[0:4], p.Time)
|
||||
binary.LittleEndian.PutUint32(buf[4:8], p.Memory)
|
||||
binary.LittleEndian.PutUint32(buf[8:12], p.Threads)
|
||||
return buf
|
||||
}
|
||||
|
||||
// DecodeArgon2Params reads 12 bytes (3 x uint32 little-endian) into Argon2Params.
|
||||
func DecodeArgon2Params(data []byte) Argon2Params {
|
||||
return Argon2Params{
|
||||
Time: binary.LittleEndian.Uint32(data[0:4]),
|
||||
Memory: binary.LittleEndian.Uint32(data[4:8]),
|
||||
Threads: binary.LittleEndian.Uint32(data[8:12]),
|
||||
}
|
||||
}
|
||||
|
||||
// DeriveKeyArgon2 derives a 32-byte key from a password and salt using Argon2id
|
||||
// with DefaultArgon2Params. This is the recommended key derivation for new code.
|
||||
func DeriveKeyArgon2(password string, salt []byte) []byte {
|
||||
p := DefaultArgon2Params()
|
||||
return argon2.IDKey([]byte(password), salt, p.Time, p.Memory, uint8(p.Threads), 32)
|
||||
}
|
||||
|
||||
// ToTrixChaCha converts a DataNode to encrypted Trix format using ChaCha20-Poly1305.
|
||||
func ToTrixChaCha(dn *datanode.DataNode, password string) ([]byte, error) {
|
||||
if password == "" {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package trix
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/Snider/Borg/pkg/datanode"
|
||||
|
|
@ -236,3 +238,85 @@ func TestToTrixChaChaWithLargeData(t *testing.T) {
|
|||
t.Fatalf("Failed to open large.bin: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Argon2id key derivation tests ---
|
||||
|
||||
func TestDeriveKeyArgon2_Good(t *testing.T) {
|
||||
salt := make([]byte, 16)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
t.Fatalf("failed to generate salt: %v", err)
|
||||
}
|
||||
|
||||
key := DeriveKeyArgon2("test-password", salt)
|
||||
if len(key) != 32 {
|
||||
t.Fatalf("expected 32-byte key, got %d bytes", len(key))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveKeyArgon2_Deterministic_Good(t *testing.T) {
|
||||
salt := []byte("fixed-salt-value")
|
||||
|
||||
key1 := DeriveKeyArgon2("same-password", salt)
|
||||
key2 := DeriveKeyArgon2("same-password", salt)
|
||||
|
||||
if !bytes.Equal(key1, key2) {
|
||||
t.Fatal("same password and salt must produce the same key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveKeyArgon2_DifferentSalt_Good(t *testing.T) {
|
||||
salt1 := []byte("salt-one-value!!")
|
||||
salt2 := []byte("salt-two-value!!")
|
||||
|
||||
key1 := DeriveKeyArgon2("same-password", salt1)
|
||||
key2 := DeriveKeyArgon2("same-password", salt2)
|
||||
|
||||
if bytes.Equal(key1, key2) {
|
||||
t.Fatal("different salts must produce different keys")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeriveKeyLegacy_Good(t *testing.T) {
|
||||
key1 := DeriveKey("backward-compat")
|
||||
key2 := DeriveKey("backward-compat")
|
||||
|
||||
if len(key1) != 32 {
|
||||
t.Fatalf("expected 32-byte key, got %d bytes", len(key1))
|
||||
}
|
||||
if !bytes.Equal(key1, key2) {
|
||||
t.Fatal("legacy DeriveKey must be deterministic")
|
||||
}
|
||||
}
|
||||
|
||||
func TestArgon2Params_Good(t *testing.T) {
|
||||
params := DefaultArgon2Params()
|
||||
|
||||
// Non-zero values
|
||||
if params.Time == 0 {
|
||||
t.Fatal("Time must be non-zero")
|
||||
}
|
||||
if params.Memory == 0 {
|
||||
t.Fatal("Memory must be non-zero")
|
||||
}
|
||||
if params.Threads == 0 {
|
||||
t.Fatal("Threads must be non-zero")
|
||||
}
|
||||
|
||||
// Encode produces 12 bytes (3 x uint32 LE)
|
||||
encoded := params.Encode()
|
||||
if len(encoded) != 12 {
|
||||
t.Fatalf("expected 12-byte encoding, got %d bytes", len(encoded))
|
||||
}
|
||||
|
||||
// Round-trip: Decode must recover original values
|
||||
decoded := DecodeArgon2Params(encoded)
|
||||
if decoded.Time != params.Time {
|
||||
t.Fatalf("Time mismatch: got %d, want %d", decoded.Time, params.Time)
|
||||
}
|
||||
if decoded.Memory != params.Memory {
|
||||
t.Fatalf("Memory mismatch: got %d, want %d", decoded.Memory, params.Memory)
|
||||
}
|
||||
if decoded.Threads != params.Threads {
|
||||
t.Fatalf("Threads mismatch: got %d, want %d", decoded.Threads, params.Threads)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue