Encrypt key with scrypt-hashed password

This commit is contained in:
Jakob Unterwurzacher 2015-09-13 21:47:18 +02:00
parent 164739b655
commit 6f9e90c414
6 changed files with 94 additions and 41 deletions

View File

@ -27,7 +27,7 @@ type nullTracer struct {}
func (nullTracer) Trace(op cluefs.FsOperTracer) {} func (nullTracer) Trace(op cluefs.FsOperTracer) {}
func NewFS(key [16]byte, backing string, useOpenssl bool) (*FS, error) { func NewFS(key []byte, backing string, useOpenssl bool) (*FS, error) {
var tracer nullTracer var tracer nullTracer
clfs, err := cluefs.NewClueFS(backing, tracer) clfs, err := cluefs.NewClueFS(backing, tracer)
if err != nil { if err != nil {

View File

@ -1,7 +1,6 @@
package cryptfs package cryptfs
import ( import (
"errors"
"io/ioutil" "io/ioutil"
"encoding/json" "encoding/json"
) )
@ -17,23 +16,30 @@ const (
) )
type confFile struct { type confFile struct {
// File the config is saved in. Lowercase => not exported to JSON. // File the config is saved to. Not exported to JSON.
filename string filename string
// Unencrypted AES key // Encrypted AES key, unlocked using a password hashed with scrypt
Key [16]byte EncryptedKey []byte
// GCM ciphertext with auth tag to verify the key is correct // Stores parameters for scrypt hashing (key derivation)
TestBlock []byte ScryptObject scryptKdf
} }
// CreateConfFile - create a new config file with "key" and write to "filename" // CreateConfFile - create a new config with a random key encrypted with
func CreateConfFile(filename string, key [16]byte) error { // "password" and write it to "filename"
func CreateConfFile(filename string, password string) error {
var cf confFile var cf confFile
cf.filename = filename cf.filename = filename
cf.Key = key
// Generate test block // Generate new random master key
cfs := NewCryptFS(cf.Key, false) key := RandBytes(KEY_LEN)
cf.TestBlock = cfs.EncryptBlock([]byte(testBlockData))
// Generate derived key from password
cf.ScryptObject = NewScryptKdf()
scryptHash := cf.ScryptObject.DeriveKey(password)
// Lock master key using password-based key
cfs := NewCryptFS(scryptHash, false)
cf.EncryptedKey = cfs.EncryptBlock(key)
// Write file to disk // Write file to disk
err := cf.WriteFile() err := cf.WriteFile()
@ -41,34 +47,39 @@ func CreateConfFile(filename string, key [16]byte) error {
return err return err
} }
// LoadConfFile - read config file from disk and verify the key using the // LoadConfFile - read config file from disk and decrypt the
// embedded TestBlock // contained key using password
func LoadConfFile(filename string) (*confFile, error) { func LoadConfFile(filename string, password string) ([]byte, error) {
var cf confFile
cf.filename = filename
// Read from disk // Read from disk
js, err := ioutil.ReadFile(filename) js, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var cf confFile
// Unmarshal
err = json.Unmarshal(js, &cf) err = json.Unmarshal(js, &cf)
if err != nil { if err != nil {
Warn.Printf("Failed to unmarshal config file\n")
return nil, err return nil, err
} }
cf.filename = filename
// Try to decrypt the test block to see if the key is correct // Generate derived key from password
// scryptHash := cf.ScryptObject.DeriveKey(password)
// Speed does not matter here. Use built-in crypto.
cfs := NewCryptFS(cf.Key, false) // Unlock master key using password-based key
d, err := cfs.DecryptBlock(cf.TestBlock) // We use stock go GCM instead of OpenSSL here as speed is not important
// and we get better error messages
cfs := NewCryptFS(scryptHash, false)
key, err := cfs.DecryptBlock(cf.EncryptedKey)
if err != nil { if err != nil {
Warn.Printf("Failed to unlock master key: %s\n", err.Error())
return nil, err return nil, err
} }
ds := string(d)
if ds != testBlockData { return key, nil
return nil, errors.New("Invalid test block content: " + ds)
}
return &cf, nil
} }
// WriteFile - write out config in JSON format to file "filename.tmp" // WriteFile - write out config in JSON format to file "filename.tmp"

View File

@ -3,6 +3,7 @@ package cryptfs
// CryptFS is the crypto backend of GoCryptFS // CryptFS is the crypto backend of GoCryptFS
import ( import (
"fmt"
"crypto/cipher" "crypto/cipher"
"crypto/aes" "crypto/aes"
) )
@ -21,16 +22,22 @@ type CryptFS struct {
cipherBS uint64 cipherBS uint64
} }
func NewCryptFS(key [16]byte, useOpenssl bool) *CryptFS { func NewCryptFS(key []byte, useOpenssl bool) *CryptFS {
b, err := aes.NewCipher(key[:]) if len(key) != KEY_LEN {
panic(fmt.Sprintf("Unsupported key length %d", len(key)))
}
b, err := aes.NewCipher(key)
if err != nil { if err != nil {
panic(err) panic(err)
} }
var gcm cipher.AEAD var gcm cipher.AEAD
if useOpenssl { if useOpenssl {
gcm = opensslGCM{key} var k16 [16]byte
copy(k16[:], key)
gcm = opensslGCM{k16}
} else { } else {
gcm, err = cipher.NewGCM(b) gcm, err = cipher.NewGCM(b)
if err != nil { if err != nil {

38
cryptfs/kdf.go Normal file
View File

@ -0,0 +1,38 @@
package cryptfs
import (
"fmt"
"golang.org/x/crypto/scrypt"
)
const (
// 1 << 16 uses 64MB of memory,
// takes 4 seconds on my Atom Z3735F netbook
SCRYPT_DEFAULT_N = 1 << 16
)
type scryptKdf struct {
Salt []byte
N int
R int
P int
KeyLen int
}
func NewScryptKdf() scryptKdf {
var s scryptKdf
s.Salt = RandBytes(KEY_LEN)
s.N = SCRYPT_DEFAULT_N
s.R = 8 // Always 8
s.P = 1 // Always 1
s.KeyLen = KEY_LEN
return s
}
func (s *scryptKdf) DeriveKey(pw string) []byte {
k, err := scrypt.Key([]byte(pw), s.Salt, s.N, s.R, s.P, s.KeyLen)
if err != nil {
panic(fmt.Sprintf("DeriveKey failed: %s", err.Error()))
}
return k
}

15
main.go
View File

@ -55,10 +55,7 @@ func main() {
} }
dir, _ := filepath.Abs(flag.Arg(0)) dir, _ := filepath.Abs(flag.Arg(0))
filename := filepath.Join(dir, cryptfs.ConfDefaultName) filename := filepath.Join(dir, cryptfs.ConfDefaultName)
r := cryptfs.RandBytes(cryptfs.KEY_LEN) err := cryptfs.CreateConfFile(filename, "test")
var key [16]byte
copy(key[:], r)
err := cryptfs.CreateConfFile(filename, key)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(ERREXIT_INIT) os.Exit(ERREXIT_INIT)
@ -86,20 +83,20 @@ func main() {
fmt.Printf("Please run \"%s --init %s\" first\n", PROGRAM_NAME, cipherdir) fmt.Printf("Please run \"%s --init %s\" first\n", PROGRAM_NAME, cipherdir)
os.Exit(ERREXIT_LOADCONF) os.Exit(ERREXIT_LOADCONF)
} }
cf, err := cryptfs.LoadConfFile(cfname) key, err := cryptfs.LoadConfFile(cfname, "test")
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
os.Exit(ERREXIT_LOADCONF) os.Exit(ERREXIT_LOADCONF)
} }
if USE_CLUEFS { if USE_CLUEFS {
cluefsFrontend(cf.Key, cipherdir, mountpoint) cluefsFrontend(key, cipherdir, mountpoint)
} else { } else {
pathfsFrontend(cf.Key, cipherdir, mountpoint, debug) pathfsFrontend(key, cipherdir, mountpoint, debug)
} }
} }
func cluefsFrontend(key [16]byte, cipherdir string, mountpoint string) { func cluefsFrontend(key []byte, cipherdir string, mountpoint string) {
cfs, err := cluefs_frontend.NewFS(key, cipherdir, USE_OPENSSL) cfs, err := cluefs_frontend.NewFS(key, cipherdir, USE_OPENSSL)
if err != nil { if err != nil {
fmt.Println(err) fmt.Println(err)
@ -138,7 +135,7 @@ func cluefsFrontend(key [16]byte, cipherdir string, mountpoint string) {
os.Exit(0) os.Exit(0)
} }
func pathfsFrontend(key [16]byte, cipherdir string, mountpoint string, debug bool){ func pathfsFrontend(key []byte, cipherdir string, mountpoint string, debug bool){
finalFs := pathfs_frontend.NewFS(key, cipherdir, USE_OPENSSL) finalFs := pathfs_frontend.NewFS(key, cipherdir, USE_OPENSSL)

View File

@ -19,7 +19,7 @@ type FS struct {
} }
// Encrypted FUSE overlay filesystem // Encrypted FUSE overlay filesystem
func NewFS(key [16]byte, backing string, useOpenssl bool) *FS { func NewFS(key []byte, backing string, useOpenssl bool) *FS {
return &FS{ return &FS{
CryptFS: cryptfs.NewCryptFS(key, useOpenssl), CryptFS: cryptfs.NewCryptFS(key, useOpenssl),
FileSystem: pathfs.NewLoopbackFileSystem(backing), FileSystem: pathfs.NewLoopbackFileSystem(backing),