summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--cmd/finditers/finditers.go44
-rw-r--r--cmd/hashpw/hashpw.go34
-rw-r--r--flake.lock6
-rw-r--r--flake.nix2
-rw-r--r--go.mod6
-rw-r--r--go.sum4
-rw-r--r--internal/pwhash/pwhash.go22
-rw-r--r--roseh.moe.go21
8 files changed, 129 insertions, 10 deletions
diff --git a/cmd/finditers/finditers.go b/cmd/finditers/finditers.go
new file mode 100644
index 0000000..2a256c8
--- /dev/null
+++ b/cmd/finditers/finditers.go
@@ -0,0 +1,44 @@
+package main
+
+import (
+ "encoding/hex"
+ "fmt"
+ "os"
+ "sort"
+ "testing"
+ "time"
+
+ "gitlab.com/rhogenson/roseh.moe/internal/pwhash"
+)
+
+var (
+ iterations int
+
+ salt, _ = hex.DecodeString("3fb84513fc3afcd6d3b230bf9ece91aaae2d2a99da17efbf7de83b21fafe3f08")
+)
+
+func BenchmarkHashIter(b *testing.B) {
+ const password = "oboe shortness ether ideology undesired fresh freezable catching mashing glimpse"
+ for b.Loop() {
+ pwhash.HashIter(password, salt, iterations)
+ }
+}
+
+func run() error {
+ for iterations = 8192; time.Duration(testing.Benchmark(BenchmarkHashIter).NsPerOp()) < 500*time.Millisecond; 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 nil
+}
+
+func main() {
+ if err := run(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
diff --git a/cmd/hashpw/hashpw.go b/cmd/hashpw/hashpw.go
new file mode 100644
index 0000000..ffbdf79
--- /dev/null
+++ b/cmd/hashpw/hashpw.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "crypto/rand"
+ "fmt"
+ "os"
+
+ "gitlab.com/rhogenson/roseh.moe/internal/pwhash"
+ "golang.org/x/term"
+)
+
+func run() error {
+ fmt.Print("Enter password:")
+ password, err := term.ReadPassword(int(os.Stdin.Fd()))
+ fmt.Println()
+ if err != nil {
+ return err
+ }
+ salt := make([]byte, 32)
+ rand.Read(salt)
+ hashedPassword, err := pwhash.Hash(string(password), salt)
+ if err != nil {
+ return err
+ }
+ fmt.Printf("notepad-password=%x\nsalt=%x\n", hashedPassword, salt)
+ return nil
+}
+
+func main() {
+ if err := run(); err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+}
diff --git a/flake.lock b/flake.lock
index ff4cd92..5f475fe 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1757545623,
- "narHash": "sha256-mCxPABZ6jRjUQx3bPP4vjA68ETbPLNz9V2pk9tO7pRQ=",
+ "lastModified": 1758589230,
+ "narHash": "sha256-zMTCFGe8aVGTEr2RqUi/QzC1nOIQ0N1HRsbqB4f646k=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "8cd5ce828d5d1d16feff37340171a98fc3bf6526",
+ "rev": "d1d883129b193f0b495d75c148c2c3a7d95789a0",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index f6122c4..0a0d016 100644
--- a/flake.nix
+++ b/flake.nix
@@ -48,7 +48,7 @@
# remember to bump this hash when your dependencies change.
# vendorHash = pkgs.lib.fakeHash;
- vendorHash = null;
+ vendorHash = "sha256-L7nK+w4CB2H3b6vL0ZoFfaRMgCmpqzQo8ThMM60C76I=";
};
});
diff --git a/go.mod b/go.mod
index f50ecfa..5e702ba 100644
--- a/go.mod
+++ b/go.mod
@@ -1,3 +1,7 @@
module gitlab.com/rhogenson/roseh.moe
-go 1.23.2
+go 1.24.0
+
+require golang.org/x/term v0.35.0
+
+require golang.org/x/sys v0.36.0 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..a7fae6a
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
+golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
+golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
diff --git a/internal/pwhash/pwhash.go b/internal/pwhash/pwhash.go
new file mode 100644
index 0000000..a17ed2e
--- /dev/null
+++ b/internal/pwhash/pwhash.go
@@ -0,0 +1,22 @@
+package pwhash
+
+import (
+ "crypto/pbkdf2"
+ "crypto/sha256"
+ "crypto/sha512"
+)
+
+const defaultIterations = 3670016 // from cmd/finditers
+
+func HashIter(password string, salt []byte, iter int) ([]byte, error) {
+ return pbkdf2.Key(sha256.New, password, salt, iter, 32)
+}
+
+func Hash(password string, salt []byte) ([]byte, error) {
+ hashed, err := HashIter(password, salt, defaultIterations)
+ if err != nil {
+ return nil, err
+ }
+ sha := sha512.Sum512(hashed)
+ return sha[:], nil
+}
diff --git a/roseh.moe.go b/roseh.moe.go
index b8e6e21..a15fd9b 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -4,7 +4,6 @@ import (
"bytes"
"crypto/ed25519"
"crypto/rand"
- "crypto/sha512"
"crypto/subtle"
"embed"
"encoding/base64"
@@ -19,6 +18,8 @@ import (
"path/filepath"
"strings"
"time"
+
+ "gitlab.com/rhogenson/roseh.moe/internal/pwhash"
)
var (
@@ -30,9 +31,10 @@ var (
)
var (
- notepadPassword []byte
- privateKey ed25519.PrivateKey
- publicKey ed25519.PublicKey
+ notepadPassword []byte
+ notepadPasswordSalt []byte
+ privateKey ed25519.PrivateKey
+ publicKey ed25519.PublicKey
)
func loadSecrets() error {
@@ -56,6 +58,11 @@ func loadSecrets() error {
}
privateKey = ed25519.NewKeyFromSeed(seed)
publicKey = privateKey.Public().(ed25519.PublicKey)
+ } else if salt, ok := bytes.CutPrefix(line, []byte("salt=")); ok {
+ notepadPasswordSalt = make([]byte, hex.DecodedLen(len(salt)))
+ if _, err := hex.Decode(notepadPasswordSalt, salt); err != nil {
+ return err
+ }
}
}
return nil
@@ -204,7 +211,11 @@ type loginTemplateArgs struct {
}
func login(w http.ResponseWriter, r *http.Request) {
- pwHash := sha512.Sum512([]byte(r.FormValue("password")))
+ 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
+ }
if subtle.ConstantTimeCompare(pwHash[:], notepadPassword) == 0 {
if err := loginTemplate.Execute(w, loginTemplateArgs{Error: true, Redirect: r.FormValue("redirect")}); err != nil {
log.Printf("Warning: login: %s", err)