diff --git a/cluefs_frontend/fe_fs.go b/cluefs_frontend/fe_fs.go index 2b12da6..9d677b2 100644 --- a/cluefs_frontend/fe_fs.go +++ b/cluefs_frontend/fe_fs.go @@ -27,7 +27,7 @@ type nullTracer struct {} 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 clfs, err := cluefs.NewClueFS(backing, tracer) if err != nil { diff --git a/cryptfs/config_file.go b/cryptfs/config_file.go index 752ed09..5651716 100644 --- a/cryptfs/config_file.go +++ b/cryptfs/config_file.go @@ -1,7 +1,6 @@ package cryptfs import ( - "errors" "io/ioutil" "encoding/json" ) @@ -17,23 +16,30 @@ const ( ) 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 - // Unencrypted AES key - Key [16]byte - // GCM ciphertext with auth tag to verify the key is correct - TestBlock []byte + // Encrypted AES key, unlocked using a password hashed with scrypt + EncryptedKey []byte + // Stores parameters for scrypt hashing (key derivation) + ScryptObject scryptKdf } -// CreateConfFile - create a new config file with "key" and write to "filename" -func CreateConfFile(filename string, key [16]byte) error { +// CreateConfFile - create a new config with a random key encrypted with +// "password" and write it to "filename" +func CreateConfFile(filename string, password string) error { var cf confFile cf.filename = filename - cf.Key = key - // Generate test block - cfs := NewCryptFS(cf.Key, false) - cf.TestBlock = cfs.EncryptBlock([]byte(testBlockData)) + // Generate new random master key + key := RandBytes(KEY_LEN) + + // 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 err := cf.WriteFile() @@ -41,34 +47,39 @@ func CreateConfFile(filename string, key [16]byte) error { return err } -// LoadConfFile - read config file from disk and verify the key using the -// embedded TestBlock -func LoadConfFile(filename string) (*confFile, error) { +// LoadConfFile - read config file from disk and decrypt the +// contained key using password +func LoadConfFile(filename string, password string) ([]byte, error) { + var cf confFile + cf.filename = filename + // Read from disk js, err := ioutil.ReadFile(filename) if err != nil { return nil, err } - var cf confFile + + // Unmarshal err = json.Unmarshal(js, &cf) if err != nil { + Warn.Printf("Failed to unmarshal config file\n") return nil, err } - cf.filename = filename - // Try to decrypt the test block to see if the key is correct - // - // Speed does not matter here. Use built-in crypto. - cfs := NewCryptFS(cf.Key, false) - d, err := cfs.DecryptBlock(cf.TestBlock) + // Generate derived key from password + scryptHash := cf.ScryptObject.DeriveKey(password) + + // Unlock master key using password-based key + // 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 { + Warn.Printf("Failed to unlock master key: %s\n", err.Error()) return nil, err } - ds := string(d) - if ds != testBlockData { - return nil, errors.New("Invalid test block content: " + ds) - } - return &cf, nil + + return key, nil } // WriteFile - write out config in JSON format to file "filename.tmp" diff --git a/cryptfs/cryptfs.go b/cryptfs/cryptfs.go index 8927d74..6380a92 100644 --- a/cryptfs/cryptfs.go +++ b/cryptfs/cryptfs.go @@ -3,6 +3,7 @@ package cryptfs // CryptFS is the crypto backend of GoCryptFS import ( + "fmt" "crypto/cipher" "crypto/aes" ) @@ -21,16 +22,22 @@ type CryptFS struct { 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 { panic(err) } var gcm cipher.AEAD if useOpenssl { - gcm = opensslGCM{key} + var k16 [16]byte + copy(k16[:], key) + gcm = opensslGCM{k16} } else { gcm, err = cipher.NewGCM(b) if err != nil { diff --git a/cryptfs/kdf.go b/cryptfs/kdf.go new file mode 100644 index 0000000..275c72e --- /dev/null +++ b/cryptfs/kdf.go @@ -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 +} diff --git a/main.go b/main.go index 6315805..77b9c10 100644 --- a/main.go +++ b/main.go @@ -55,10 +55,7 @@ func main() { } dir, _ := filepath.Abs(flag.Arg(0)) filename := filepath.Join(dir, cryptfs.ConfDefaultName) - r := cryptfs.RandBytes(cryptfs.KEY_LEN) - var key [16]byte - copy(key[:], r) - err := cryptfs.CreateConfFile(filename, key) + err := cryptfs.CreateConfFile(filename, "test") if err != nil { fmt.Println(err) os.Exit(ERREXIT_INIT) @@ -86,20 +83,20 @@ func main() { fmt.Printf("Please run \"%s --init %s\" first\n", PROGRAM_NAME, cipherdir) os.Exit(ERREXIT_LOADCONF) } - cf, err := cryptfs.LoadConfFile(cfname) + key, err := cryptfs.LoadConfFile(cfname, "test") if err != nil { fmt.Println(err) os.Exit(ERREXIT_LOADCONF) } if USE_CLUEFS { - cluefsFrontend(cf.Key, cipherdir, mountpoint) + cluefsFrontend(key, cipherdir, mountpoint) } 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) if err != nil { fmt.Println(err) @@ -138,7 +135,7 @@ func cluefsFrontend(key [16]byte, cipherdir string, mountpoint string) { 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) diff --git a/pathfs_frontend/fs.go b/pathfs_frontend/fs.go index 1e4a1f3..5a52cfc 100644 --- a/pathfs_frontend/fs.go +++ b/pathfs_frontend/fs.go @@ -19,7 +19,7 @@ type FS struct { } // 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{ CryptFS: cryptfs.NewCryptFS(key, useOpenssl), FileSystem: pathfs.NewLoopbackFileSystem(backing),