// - descriptor computation (double SHA512)
package crypto
-/*
-#cgo LDFLAGS: -largon2
-#include <stdlib.h> // malloc(), free()
-#include <argon2.h>
-*/
-import "C"
-
import (
"crypto/aes"
"crypto/cipher"
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
- "unsafe"
"github.com/pkg/errors"
+ "golang.org/x/crypto/argon2"
"golang.org/x/crypto/hkdf"
"github.com/google/fscrypt/metadata"
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
-}
-
// ComputeDescriptor computes the descriptor for a given cryptographic key. In
// keeping with the process used in e4crypt, this uses the initial bytes
// (formatted as hexadecimal) of the double application of SHA512 on the key.
return hex.EncodeToString(h2[:length])
}
-/*
-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
-*/
+// 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. For more information, see the documentation at
+// https://godoc.org/golang.org/x/crypto/argon2.
func PassphraseHash(passphrase *Key, salt []byte, costs *metadata.HashingCosts) (*Key, error) {
- if err := util.CheckValidLength(metadata.SaltLen, len(salt)); err != nil {
- return nil, errors.Wrap(err, "passphrase hashing salt")
- }
- if err := costs.CheckValidity(); err != nil {
- return nil, errors.Wrap(err, "passphrase hashing costs")
- }
+ t := uint32(costs.Time)
+ m := uint32(costs.Memory)
+ p := uint8(costs.Parallelism)
+ key := argon2.IDKey(passphrase.data, salt, t, m, p, metadata.InternalKeyLen)
- // This key will hold the hashing output
hash, err := newBlankKey(metadata.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.SystemError("argon2: " + errorString)
- }
-
+ copy(hash.data, key)
return hash, nil
}
}
}
-func TestBadTime(t *testing.T) {
- pk, err := fakePassphraseKey()
- if err != nil {
- t.Fatal(err)
- }
- defer pk.Wipe()
-
- costs := *hashTestCases[0].costs
- costs.Time = 0
- _, err = PassphraseHash(pk, fakeSalt, &costs)
- if err == nil {
- t.Errorf("time cost of %d should be invalid", costs.Time)
- }
-}
-
-func TestBadMemory(t *testing.T) {
- pk, err := fakePassphraseKey()
- if err != nil {
- t.Fatal(err)
- }
- defer pk.Wipe()
-
- costs := *hashTestCases[0].costs
- costs.Memory = 7
- _, err = PassphraseHash(pk, fakeSalt, &costs)
- if err == nil {
- t.Errorf("memory cost of %d should be invalid", costs.Memory)
- }
-}
-
-func TestBadParallelism(t *testing.T) {
- pk, err := fakePassphraseKey()
- if err != nil {
- t.Fatal(err)
- }
- defer pk.Wipe()
-
- costs := *hashTestCases[0].costs
- costs.Parallelism = 1 << 24
- costs.Memory = 1 << 27 // Running n threads requires at least 8*n memory
- _, err = PassphraseHash(pk, fakeSalt, &costs)
- if err == nil {
- t.Errorf("parallelism cost of %d should be invalid", costs.Parallelism)
- }
-}
-
-func TestBadSalt(t *testing.T) {
- pk, err := fakePassphraseKey()
- if err != nil {
- t.Fatal(err)
- }
- defer pk.Wipe()
-
- _, err = PassphraseHash(pk, []byte{1, 2, 3, 4}, hashTestCases[0].costs)
- if err == nil {
- t.Error("too short of salt should be invalid")
- }
-}
-
func BenchmarkWrap(b *testing.B) {
for n := 0; n < b.N; n++ {
Wrap(fakeWrappingKey, fakeValidPolicyKey)