summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-09-25 17:54:29 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-09-25 17:54:29 -0700
commit251ea4f8fcfd0960f6a627501eb556e109a3e2b6 (patch)
tree4bcc15172503228559b22ee31dacf5ba2a118971
parent69cd1c740997cf2ccb6a710826a06ee82e41dbe7 (diff)
downloadroseh.moe-251ea4f8fcfd0960f6a627501eb556e109a3e2b6.tar.zst
Use AES-CTR-HMAC-SHA256 instead of AES-GCM
-rw-r--r--internal/pwhash/pwhash.go25
-rw-r--r--roseh.moe.go75
2 files changed, 72 insertions, 28 deletions
diff --git a/internal/pwhash/pwhash.go b/internal/pwhash/pwhash.go
index d886746..9ef5d2a 100644
--- a/internal/pwhash/pwhash.go
+++ b/internal/pwhash/pwhash.go
@@ -1,24 +1,33 @@
package pwhash
import (
+ "crypto/hkdf"
"crypto/pbkdf2"
- "crypto/sha512"
+ "crypto/sha256"
)
-const SaltLen = sha512.Size
+const SaltLen = 2 * sha256.Size
-const defaultIterations = 1220340 // from cmd/finditers
+const defaultIterations = 3749890 // from cmd/finditers
func HashIter(password string, salt []byte, iter int) ([]byte, error) {
- return pbkdf2.Key(sha512.New, password, salt, iter, sha512.Size)
+ return pbkdf2.Key(sha256.New, password, salt, iter, sha256.Size)
}
-func Hash(password string, salt []byte) (key, pwHash []byte, err error) {
- const aesKeySize = 32
+func Hash(password string, salt []byte) (rawKey, pwHash []byte, err error) {
+ const hashLen = 32
- key, err = HashIter(password, salt, defaultIterations)
+ key, err := HashIter(password, salt[:sha256.Size], defaultIterations)
if err != nil {
return nil, nil, err
}
- return key[:aesKeySize], key[aesKeySize:], nil
+ key, err = hkdf.Extract(sha256.New, key, salt[sha256.Size:])
+ if err != nil {
+ return nil, nil, err
+ }
+ pwHash, err = hkdf.Expand(sha256.New, key, "pwhash", hashLen)
+ if err != nil {
+ return nil, nil, err
+ }
+ return key, pwHash, nil
}
diff --git a/roseh.moe.go b/roseh.moe.go
index 2cdf68f..3c5e7a9 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -4,6 +4,7 @@ import (
"bytes"
"crypto/aes"
"crypto/cipher"
+ "crypto/hkdf"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
@@ -90,34 +91,58 @@ func verify(msg []byte) ([]byte, bool) {
var errNoKey = errors.New("not logged in")
-func loadEncryptionKey() (cipher.AEAD, error) {
+const (
+ aesKeyLen = 32
+ nonceLen = aes.BlockSize
+ certLen = sha256.Size
+)
+
+func encrypt(msg string) ([]byte, error) {
encryptionKeyMu.Lock()
key := encryptionKey
encryptionKeyMu.Unlock()
if key == nil {
return nil, errNoKey
}
- block, err := aes.NewCipher(key)
+ block, err := aes.NewCipher(key[:aesKeyLen])
if err != nil {
return nil, err
}
- return cipher.NewGCMWithRandomNonce(block)
+ buf := make([]byte, nonceLen+len(msg)+certLen)
+ nonce := buf[:nonceLen]
+ rand.Read(nonce)
+ cipher.NewCTR(block, nonce).XORKeyStream(buf[nonceLen:], []byte(msg))
+ cipherText := buf[:nonceLen+len(msg)]
+ mac := hmac.New(sha256.New, key[aesKeyLen:])
+ mac.Write(cipherText)
+ return mac.Sum(cipherText), nil
}
-func encrypt(msg []byte) ([]byte, error) {
- aead, err := loadEncryptionKey()
- if err != nil {
- return nil, err
+func decrypt(msg []byte) (string, error) {
+ encryptionKeyMu.Lock()
+ key := encryptionKey
+ encryptionKeyMu.Unlock()
+ if key == nil {
+ return "", errNoKey
}
- return aead.Seal(nil, nil, msg, nil), nil
-}
-
-func decrypt(msg []byte) ([]byte, error) {
- aead, err := loadEncryptionKey()
+ if len(msg) < certLen+nonceLen {
+ return "", fmt.Errorf("message too short")
+ }
+ msg, messageMAC := msg[:len(msg)-certLen], msg[len(msg)-certLen:]
+ mac := hmac.New(sha256.New, key[aesKeyLen:])
+ mac.Write(msg)
+ expectedMAC := mac.Sum(nil)
+ if !hmac.Equal(messageMAC, expectedMAC) {
+ return "", fmt.Errorf("bad signature")
+ }
+ block, err := aes.NewCipher(key[:aesKeyLen])
if err != nil {
- return nil, err
+ return "", err
}
- return aead.Open(nil, nil, msg, nil)
+ nonce, msg := msg[:nonceLen], msg[nonceLen:]
+ buf := make([]byte, len(msg))
+ cipher.NewCTR(block, nonce).XORKeyStream(buf, msg)
+ return string(buf), nil
}
var (
@@ -259,7 +284,7 @@ type loginTemplateArgs struct {
}
func login(w http.ResponseWriter, r *http.Request) {
- key, pwHash, err := pwhash.Hash(r.FormValue("password"), notepadPasswordSalt)
+ rawKey, pwHash, err := pwhash.Hash(r.FormValue("password"), notepadPasswordSalt)
if err != nil {
http.Error(w, fmt.Sprintf("Unable to hash password: %s", err), http.StatusInternalServerError)
return
@@ -271,10 +296,20 @@ func login(w http.ResponseWriter, r *http.Request) {
return
}
encryptionKeyMu.Lock()
- if encryptionKey == nil {
- encryptionKey = key
- }
+ currentKey := encryptionKey
encryptionKeyMu.Unlock()
+ if currentKey == nil {
+ key, err := hkdf.Expand(sha256.New, rawKey, "encrypt", aesKeyLen+certLen)
+ if err != nil {
+ http.Error(w, fmt.Sprintf("failed to derive encryption key: %s", err), http.StatusInternalServerError)
+ return
+ }
+ encryptionKeyMu.Lock()
+ if encryptionKey == nil {
+ encryptionKey = key
+ }
+ encryptionKeyMu.Unlock()
+ }
attachCookie(w)
http.Redirect(w, r, "/notepad", http.StatusSeeOther)
}
@@ -288,7 +323,7 @@ func readNotepad() (string, error) {
if err != nil {
return "", err
}
- return string(decrypted), nil
+ return decrypted, nil
}
var (
@@ -332,7 +367,7 @@ func saveNote(w http.ResponseWriter, r *http.Request) error {
if _, ok := cookieAuth(w, r); !ok {
return fmt.Errorf("not logged in")
}
- encrypted, err := encrypt([]byte(r.FormValue("content")))
+ encrypted, err := encrypt(r.FormValue("content"))
if err != nil {
return err
}