Encrypt key with scrypt-hashed password
This commit is contained in:
parent
164739b655
commit
6f9e90c414
@ -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 {
|
||||||
|
@ -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"
|
||||||
|
@ -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
38
cryptfs/kdf.go
Normal 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
15
main.go
@ -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)
|
||||||
|
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user