-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathx25519.go
More file actions
65 lines (58 loc) · 2.66 KB
/
x25519.go
File metadata and controls
65 lines (58 loc) · 2.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package agekd
import (
"crypto/hkdf"
"crypto/sha256"
"fmt"
"strings"
"filippo.io/age"
"github.com/awnumar/agekd/bech32"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/curve25519"
)
// X25519IdentityFromKey derives an age X25519 identity from a high-entropy key. Callers are responsible for
// ensuring that the provided key is suitably generated, e.g. 32 bytes read from crypto/rand.
//
// For post-quantum security, use HybridIdentityFromKey instead.
func X25519IdentityFromKey(key, salt []byte) (*age.X25519Identity, error) {
secretKey, err := hkdf.Key(sha256.New, key, salt, kdfLabelX25519, curve25519.ScalarSize)
if err != nil {
return nil, fmt.Errorf("failed to read randomness from hkdf: %w", err)
}
return newX25519IdentityFromScalar(secretKey)
}
// X25519IdentityFromPassword derives an age X25519 identity from a password using Argon2id, with strong default parameters.
//
// For post-quantum security, use HybridIdentityFromPassword instead.
func X25519IdentityFromPassword(password, salt []byte) (*age.X25519Identity, error) {
return X25519IdentityFromPasswordWithParameters(password, salt, DefaultArgon2idTime, DefaultArgon2idMemory, DefaultArgon2idThreads)
}
// X25519IdentityFromPasswordWithParameters derives an age X25519 identity from a password, with custom Argon2id parameters.
//
// For post-quantum security, use HybridIdentityFromPasswordWithParameters instead.
func X25519IdentityFromPasswordWithParameters(password, salt []byte, argon2idTime, argon2idMemory uint32, argon2idThreads uint8) (*age.X25519Identity, error) {
return newX25519IdentityFromScalar(argon2.IDKey(password, saltWithLabel(salt), argon2idTime, argon2idMemory, argon2idThreads, curve25519.ScalarSize))
}
// newX25519IdentityFromScalar returns a new X25519Identity from a raw Curve25519 scalar.
//
// Age does not provide a method to construct an X25519Identity using a secret key, so the
// workaround we apply here is to create an encoded string key and ask age to parse it into
// its own *age.X25519Identity type.
//
// Based on: https://github.com/FiloSottile/age/blob/v1.2.0/x25519.go
func newX25519IdentityFromScalar(secretKey []byte) (*age.X25519Identity, error) {
if len(secretKey) != curve25519.ScalarSize {
return nil, fmt.Errorf("invalid X25519 secret key")
}
s, err := bech32.Encode("AGE-SECRET-KEY-", secretKey)
if err != nil {
return nil, fmt.Errorf("failed to bech32 encode secret key: %w", err)
}
return age.ParseX25519Identity(strings.ToUpper(s))
}
// saltWithLabel appends the bound kdfLabel to the provided salt.
func saltWithLabel(salt []byte) []byte {
s := make([]byte, 0, len(salt)+len(kdfLabelX25519))
s = append(s, salt...)
s = append(s, kdfLabelX25519...)
return s
}