summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-09-26 09:12:27 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-09-26 09:12:27 -0700
commit768d3629b938196d132b80864204d7b6a9683a9e (patch)
treea147fbd27c911b6a1ec38c6492c02fd5f7b93456
parent6cb1031bcbea0c13fb49f9ae445641aed53d2e8e (diff)
downloadroseh.moe-768d3629b938196d132b80864204d7b6a9683a9e.tar.zst
Turn up all the parameters
Hopefully this makes it secure 😳
-rw-r--r--internal/cryptoutil/cryptoutil.go54
-rw-r--r--tools/finditers/finditers.go25
2 files changed, 42 insertions, 37 deletions
diff --git a/internal/cryptoutil/cryptoutil.go b/internal/cryptoutil/cryptoutil.go
index b58282b..4ed600d 100644
--- a/internal/cryptoutil/cryptoutil.go
+++ b/internal/cryptoutil/cryptoutil.go
@@ -9,24 +9,27 @@ import (
"crypto/hmac"
"crypto/pbkdf2"
"crypto/rand"
- "crypto/sha256"
+ "crypto/sha512"
"crypto/subtle"
"encoding/hex"
"errors"
)
const (
- defaultIterations = 3749890 // from cmd/finditers
+ defaultIterations = 4718580 // from cmd/finditers
- saltSize = 2 * sha256.Size
- hashSize = 32
- certSize = sha256.Size
- aesKeySize = 32
- nonceSize = aes.BlockSize
+ oneSaltSize = 64
+ hashSize = 64
+ certSize = sha512.Size
+ aesKeySize = 32
+ nonceSize = aes.BlockSize
)
+// SaltSize is the expected salt length for HashIter.
+const SaltSize = 2 * oneSaltSize
+
// A PasswordHash must be PasswordHashSize bytes.
-const PasswordHashSize = saltSize + hashSize
+const PasswordHashSize = SaltSize + hashSize
// A PasswordHash is derived from the user's password and can be passed to
// CheckPassword to verify if two passwords match. A PasswordHash must be
@@ -42,14 +45,14 @@ type RawKey struct {
}
// An HMACKey must be HMACKeySize bytes.
-const HMACKeySize = sha256.Size
+const HMACKeySize = sha512.BlockSize
// An HMAC key can be used to symmetrically sign and verify messages
-// using HMAC-SHA256. An HMACKey must be HMACKeyLen bytes.
+// using HMAC-SHA512. An HMACKey must be HMACKeyLen bytes.
type HMACKey []byte
// A SignedMessage is a message that has been cryptographically signed
-// with HMAC-SHA256.
+// with HMAC-SHA512.
type SignedMessage []byte
// An EncryptionKey must be EncryptionKeySize bytes.
@@ -59,33 +62,34 @@ const EncryptionKeySize = aesKeySize + HMACKeySize
// must be EncryptionKeySize bytes.
type EncryptionKey []byte
-// An EncryptedMessage is encrypted with AES-256-CTR-HMAC-SHA256.
+// An EncryptedMessage is encrypted with AES-256-CTR-HMAC-SHA512.
type EncryptedMessage []byte
-// HashIter runs PBKDF2-SHA256 for iter iterations. Useful for benchmarking.
+// HashIter runs PBKDF2-SHA512 for iter iterations. Useful for benchmarking. The
+// salt must be SaltSize bytes.
func HashIter(password string, salt []byte, iter int) ([]byte, error) {
- return pbkdf2.Key(sha256.New, password, salt, iter, sha256.Size)
+ return pbkdf2.Key(sha512.New, password, salt, iter, sha512.Size)
}
func hashWithSalt(password string, salt []byte) (RawKey, []byte, error) {
- hash, err := HashIter(password, salt[:sha256.Size], defaultIterations)
+ hash, err := HashIter(password, salt[:oneSaltSize], defaultIterations)
if err != nil {
return RawKey{}, nil, err
}
- key, err := hkdf.Extract(sha256.New, hash, salt[sha256.Size:])
+ key, err := hkdf.Extract(sha512.New, hash, salt[oneSaltSize:])
if err != nil {
return RawKey{}, nil, err
}
- pwHash, err := hkdf.Expand(sha256.New, key, "pwhash", hashSize)
+ pwHash, err := hkdf.Expand(sha512.New, key, "pwhash", hashSize)
if err != nil {
return RawKey{}, nil, err
}
return RawKey{key}, pwHash, nil
}
-// Hash hashes a user password using PBKDF2-SHA256.
+// Hash hashes a user password using PBKDF2-SHA512.
func Hash(password string) (PasswordHash, error) {
- salt := make([]byte, saltSize, saltSize+hashSize)
+ salt := make([]byte, SaltSize, SaltSize+hashSize)
rand.Read(salt)
_, pwHash, err := hashWithSalt(password, salt)
if err != nil {
@@ -97,7 +101,7 @@ func Hash(password string) (PasswordHash, error) {
// CheckPassword verifies password against a PasswordHash and returns an
// EncryptionKey derived from the password if successful.
func (h PasswordHash) CheckPassword(password string) (RawKey, error) {
- salt, expectedHash := h[:saltSize], h[saltSize:]
+ salt, expectedHash := h[:SaltSize], h[SaltSize:]
key, pwHash, err := hashWithSalt(password, salt)
if err != nil {
return RawKey{}, err
@@ -108,9 +112,9 @@ func (h PasswordHash) CheckPassword(password string) (RawKey, error) {
return key, nil
}
-// Sign generates an HMAC-SHA256 signature and appends it to msg.
+// Sign generates an HMAC-SHA512 signature and appends it to msg.
func (k HMACKey) Sign(msg []byte) SignedMessage {
- mac := hmac.New(sha256.New, k)
+ mac := hmac.New(sha512.New, k)
mac.Write(msg)
return mac.Sum(msg)
}
@@ -122,7 +126,7 @@ func (k HMACKey) Verify(msg SignedMessage) ([]byte, bool) {
return nil, false
}
msg, sig := msg[:len(msg)-certSize], msg[len(msg)-certSize:]
- mac := hmac.New(sha256.New, k)
+ mac := hmac.New(sha512.New, k)
mac.Write(msg)
if !hmac.Equal(sig, mac.Sum(nil)) {
return nil, false
@@ -132,10 +136,10 @@ func (k HMACKey) Verify(msg SignedMessage) ([]byte, bool) {
// EncryptionKey derives an EncryptionKey.
func (k RawKey) EncryptionKey() (EncryptionKey, error) {
- return hkdf.Expand(sha256.New, k.key, "encrypt", EncryptionKeySize)
+ return hkdf.Expand(sha512.New, k.key, "encrypt", EncryptionKeySize)
}
-// Encrypt encrypts a message with AES-256-CTR-HMAC-SHA256.
+// Encrypt encrypts a message with AES-256-CTR-HMAC-SHA512.
func (k EncryptionKey) Encrypt(msg []byte) (EncryptedMessage, error) {
aesKey, hmacKey := k[:aesKeySize], HMACKey(k[aesKeySize:])
block, err := aes.NewCipher(aesKey)
diff --git a/tools/finditers/finditers.go b/tools/finditers/finditers.go
index cd144fa..746242b 100644
--- a/tools/finditers/finditers.go
+++ b/tools/finditers/finditers.go
@@ -1,8 +1,8 @@
package main
import (
- "encoding/hex"
"fmt"
+ "math/rand/v2"
"os"
"sort"
"testing"
@@ -11,18 +11,17 @@ import (
"gitlab.com/rhogenson/roseh.moe/internal/cryptoutil"
)
-func mustHex(s string) []byte {
- bytes, err := hex.DecodeString(s)
- if err != nil {
- panic(fmt.Sprintf("mustHex: bad hex %q: %s", s, err))
- }
- return bytes
-}
-
var (
iterations int
- salt = mustHex("3fb84513fc3afcd6d3b230bf9ece91aaae2d2a99da17efbf7de83b21fafe3f08")
+ salt = func() []byte {
+ r := rand.New(new(rand.PCG))
+ salt := make([]byte, cryptoutil.SaltSize)
+ for i := range salt {
+ salt[i] = byte(r.IntN(256))
+ }
+ return salt
+ }()
)
func BenchmarkHashIter(b *testing.B) {
@@ -33,13 +32,15 @@ func BenchmarkHashIter(b *testing.B) {
}
func run() error {
- for iterations = 8192; time.Duration(testing.Benchmark(BenchmarkHashIter).NsPerOp()) < 500*time.Millisecond; iterations *= 2 {
+ const targetDuration = 2 * time.Second
+
+ for iterations = 8192; time.Duration(testing.Benchmark(BenchmarkHashIter).NsPerOp()) < targetDuration; iterations *= 2 {
}
lo := iterations / 2
hi := iterations
fmt.Println(lo + sort.Search(hi-lo, func(i int) bool {
iterations = lo + i
- return time.Duration(testing.Benchmark(BenchmarkHashIter).NsPerOp()) > 500*time.Millisecond
+ return time.Duration(testing.Benchmark(BenchmarkHashIter).NsPerOp()) > targetDuration
}))
return nil
}