commit 2d7813749e677b4bfc91d376379cf853410c7e6a
parent 3bb07d2fe41caf6143c14b9f25e72b7f6fb9531c
Author: Sean Enck <sean@ttypty.com>
Date: Sat, 16 Jul 2022 19:29:24 -0400
use pbkdf2 for padding keyphrase
Diffstat:
2 files changed, 36 insertions(+), 17 deletions(-)
diff --git a/internal/encrypt/core.go b/internal/encrypt/core.go
@@ -2,6 +2,7 @@ package encrypt
import (
"crypto/rand"
+ "crypto/sha512"
"errors"
"io"
random "math/rand"
@@ -13,12 +14,14 @@ import (
"github.com/enckse/lockbox/internal/inputs"
"github.com/google/shlex"
"golang.org/x/crypto/nacl/secretbox"
+ "golang.org/x/crypto/pbkdf2"
)
const (
keyLength = 32
nonceLength = 24
padLength = 256
+ saltLength = 16
// PlainKeyMode is plaintext based key resolution.
PlainKeyMode = "plaintext"
// CommandKeyMode will run an external command to get the key (from stdout).
@@ -88,18 +91,21 @@ func newLockbox(key, keyMode, file string) (Lockbox, error) {
return Lockbox{}, errors.New("key is empty")
}
- if len(b) > keyLength {
- return Lockbox{}, errors.New("key is too large for use")
- }
-
- for len(b) < keyLength {
- b = append(b, byte(0))
- }
var secretKey [keyLength]byte
copy(secretKey[:], b)
return Lockbox{secret: secretKey, file: file}, nil
}
+func pad(salt, key []byte) ([keyLength]byte, error) {
+ d := pbkdf2.Key(key, salt, 4096, keyLength, sha512.New)
+ if len(d) != keyLength {
+ return [keyLength]byte{}, errors.New("invalid key result from pad")
+ }
+ var obj [keyLength]byte
+ copy(obj[:], d[:keyLength])
+ return obj, nil
+}
+
func getKey(keyMode, name string) ([]byte, error) {
var data []byte
switch keyMode {
@@ -145,23 +151,40 @@ func (l Lockbox) Encrypt(datum []byte) error {
if _, err := io.ReadFull(rand.Reader, padding[:]); err != nil {
return err
}
+ var salt [saltLength]byte
+ if _, err := io.ReadFull(rand.Reader, salt[:]); err != nil {
+ return err
+ }
var write []byte
write = append(write, byte(padTo))
write = append(write, padding[0:padTo]...)
write = append(write, data...)
- encrypted := secretbox.Seal(nonce[:], write, &nonce, &l.secret)
- return os.WriteFile(l.file, encrypted, 0600)
+ key, err := pad(salt[:], l.secret[:])
+ if err != nil {
+ return err
+ }
+ encrypted := secretbox.Seal(nonce[:], write, &nonce, &key)
+ var persist []byte
+ persist = append(persist, salt[:]...)
+ persist = append(persist, encrypted...)
+ return os.WriteFile(l.file, persist, 0600)
}
// Decrypt will decrypt an object from file.
func (l Lockbox) Decrypt() ([]byte, error) {
var nonce [nonceLength]byte
+ var salt [saltLength]byte
encrypted, err := os.ReadFile(l.file)
if err != nil {
return nil, err
}
- copy(nonce[:], encrypted[:nonceLength])
- decrypted, ok := secretbox.Open(nil, encrypted[nonceLength:], &nonce, &l.secret)
+ copy(salt[:], encrypted[:saltLength])
+ copy(nonce[:], encrypted[saltLength:saltLength+nonceLength])
+ key, err := pad(salt[:], l.secret[:])
+ if err != nil {
+ return nil, err
+ }
+ decrypted, ok := secretbox.Open(nil, encrypted[saltLength+nonceLength:], &nonce, &key)
if !ok {
return nil, errors.New("decrypt not ok")
}
diff --git a/internal/encrypt/core_test.go b/internal/encrypt/core_test.go
@@ -56,17 +56,13 @@ func TestEmptyKey(t *testing.T) {
func TestKeyLength(t *testing.T) {
val := ""
- for i := 0; i < 32; i++ {
+ for i := 0; i < keyLength+10; i++ {
val = fmt.Sprintf("a%s", val)
_, err := NewLockbox(LockboxOptions{KeyMode: PlainKeyMode, Key: val})
if err != nil {
t.Error("no error expected")
}
}
- _, err := NewLockbox(LockboxOptions{KeyMode: PlainKeyMode, Key: fmt.Sprintf("%sa", val)})
- if err == nil || err.Error() != "key is too large for use" {
- t.Errorf("invalid error: %v", err)
- }
}
func TestUnknownMode(t *testing.T) {
@@ -87,7 +83,7 @@ func TestEncryptDecryptPlainText(t *testing.T) {
}
d, err := e.Decrypt()
if err != nil {
- t.Errorf("failed to encrypt: %v", err)
+ t.Errorf("failed to decrypt: %v", err)
}
if string(d) != string(data) {
t.Error("data mismatch")