aboutsummaryrefslogtreecommitdiff
path: root/crypto/crypto.go
diff options
context:
space:
mode:
Diffstat (limited to 'crypto/crypto.go')
-rw-r--r--crypto/crypto.go94
1 files changed, 89 insertions, 5 deletions
diff --git a/crypto/crypto.go b/crypto/crypto.go
index 5eeff50..d11dce2 100644
--- a/crypto/crypto.go
+++ b/crypto/crypto.go
@@ -21,14 +21,23 @@
// - Key management (key.go)
// - Securely holding keys in memory
// - Inserting keys into the keyring
+// - Making recovery keys
// - Randomness (rand.go)
// - Cryptographic algorithms (crypto.go)
// - encryption (AES256-CTR)
// - authentication (SHA256-based HMAC)
// - key stretching (SHA256-based HKDF)
// - key wrapping/unwrapping (Encrypt then MAC)
+// - passphrase-based key derivation (Argon2id)
package crypto
+/*
+#cgo LDFLAGS: -largon2
+#include <stdlib.h> // malloc(), free()
+#include <argon2.h>
+*/
+import "C"
+
import (
"crypto/aes"
"crypto/cipher"
@@ -36,6 +45,7 @@ import (
"crypto/sha256"
"fmt"
"io"
+ "unsafe"
"golang.org/x/crypto/hkdf"
"golang.org/x/sys/unix"
@@ -54,7 +64,7 @@ const (
PolicyKeyLen = unix.FS_MAX_KEY_SIZE
)
-// "name" has invalid length if expected != actual
+// checkInputLength panics if "name" has invalid length (expected != actual)
func checkInputLength(name string, expected, actual int) {
if expected != actual {
util.NeverError(util.InvalidLengthError(name, expected, actual))
@@ -80,9 +90,9 @@ func stretchKey(key *Key) (encKey, authKey *Key) {
return
}
-// Runs AES256-CTR on the input using the provided key and iv. This function can
-// be used to either encrypt or decrypt input of any size. Note that input and
-// output must be the same size.
+// aesCTR runs AES256-CTR on the input using the provided key and iv. This
+// function can be used to either encrypt or decrypt input of any size. Note
+// that input and output must be the same size.
func aesCTR(key *Key, iv, input, output []byte) {
checkInputLength("aesCTR key", InternalKeyLen, key.Len())
checkInputLength("aesCTR iv", IVLen, len(iv))
@@ -95,7 +105,7 @@ func aesCTR(key *Key, iv, input, output []byte) {
stream.XORKeyStream(output, input)
}
-// Get a HMAC (with a SHA256-based hash) of some data using the provided key.
+// getHMAC returns the SHA256-based HMAC of some data using the provided key.
func getHMAC(key *Key, data ...[]byte) []byte {
checkInputLength("hmac key", InternalKeyLen, key.Len())
@@ -166,3 +176,77 @@ func Unwrap(wrappingKey *Key, data *metadata.WrappedKeyData) (*Key, error) {
return secretKey, nil
}
+
+// newArgon2Context creates an argon2_context C struct given the hash and
+// passphrase keys, salt and costs. The structure must be freed by the caller.
+func newArgon2Context(hash, passphrase *Key,
+ salt []byte, costs *metadata.HashingCosts) *C.argon2_context {
+
+ ctx := (*C.argon2_context)(C.malloc(C.sizeof_argon2_context))
+
+ ctx.out = (*C.uint8_t)(util.Ptr(hash.data))
+ ctx.outlen = C.uint32_t(hash.Len())
+
+ ctx.pwd = (*C.uint8_t)(util.Ptr(passphrase.data))
+ ctx.pwdlen = C.uint32_t(passphrase.Len())
+
+ ctx.salt = (*C.uint8_t)(util.Ptr(salt))
+ ctx.saltlen = C.uint32_t(len(salt))
+
+ ctx.secret = nil // We don't use the secret field.
+ ctx.secretlen = 0
+ ctx.ad = nil // We don't use the associated data field.
+ ctx.adlen = 0
+
+ ctx.t_cost = C.uint32_t(costs.Time)
+ ctx.m_cost = C.uint32_t(costs.Memory)
+ ctx.lanes = C.uint32_t(costs.Parallelism)
+
+ ctx.threads = ctx.lanes
+ ctx.version = C.ARGON2_VERSION_13
+
+ // We use the built in malloc/free for memory.
+ ctx.allocate_cbk = nil
+ ctx.free_cbk = nil
+ ctx.flags = C.ARGON2_FLAG_CLEAR_PASSWORD
+
+ return ctx
+}
+
+/*
+PassphraseHash uses Argon2id to produce a Key given the passphrase, salt, and
+hashing costs. This method is designed to take a long time and consume
+considerable memory. On success, passphrase will no longer have valid data.
+However, the caller should still call passphrase.Wipe().
+
+Argon2 is the winning algorithm of the Password Hashing Competition
+(see: https://password-hashing.net). It is designed to be "memory hard"
+in that a large amount of memory is required to compute the hash value.
+This makes it hard to use specialized hardware like GPUs and ASICs. We
+use it in "id" mode to provide extra protection against side-channel
+attacks. For more info see: https://github.com/P-H-C/phc-winner-argon2
+*/
+func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) {
+ if len(salt) != SaltLen {
+ return nil, util.InvalidLengthError("salt", SaltLen, len(salt))
+ }
+
+ // This key will hold the hashing output
+ hash, err := newBlankKey(InternalKeyLen)
+ if err != nil {
+ return nil, err
+ }
+
+ ctx := newArgon2Context(hash, passphrase, salt, costs)
+ defer C.free(unsafe.Pointer(ctx))
+
+ // Run the hashing function (translating the error if there is one)
+ returnCode := C.argon2id_ctx(ctx)
+ if returnCode != C.ARGON2_OK {
+ hash.Wipe()
+ errorString := C.GoString(C.argon2_error_message(returnCode))
+ return nil, util.SystemErrorF("argon2: %s", errorString)
+ }
+
+ return hash, nil
+}