diff --git a/.travis.yml b/.travis.yml index 5fea238..20fc6c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ script: - go build - ./build-without-openssl.bash - ./build.bash + - ./gocryptfs -speed - ./test.bash # fuse on travis diff --git a/cli_args.go b/cli_args.go index 25392c4..f52d265 100644 --- a/cli_args.go +++ b/cli_args.go @@ -18,7 +18,7 @@ type argContainer struct { debug, init, zerokey, fusedebug, openssl, passwd, fg, version, plaintextnames, quiet, nosyslog, wpanic, longnames, allow_other, ro, reverse, aessiv, nonempty, raw64, - noprealloc bool + noprealloc, speed bool masterkey, mountpoint, cipherdir, cpuprofile, extpass, memprofile, ko, passfile, ctlsock, fsname string // Configuration file name override @@ -110,6 +110,7 @@ func parseCliOpts() (args argContainer) { flagSet.BoolVar(&args.nonempty, "nonempty", false, "Allow mounting over non-empty directories") flagSet.BoolVar(&args.raw64, "raw64", false, "Use unpadded base64 for file names") flagSet.BoolVar(&args.noprealloc, "noprealloc", false, "Disable preallocation before writing") + flagSet.BoolVar(&args.speed, "speed", false, "Run crypto speed test") flagSet.StringVar(&args.masterkey, "masterkey", "", "Mount with explicit master key") flagSet.StringVar(&args.cpuprofile, "cpuprofile", "", "Write cpu profile to specified file") flagSet.StringVar(&args.memprofile, "memprofile", "", "Write memory profile to specified file") diff --git a/gocryptfs.gz b/gocryptfs.gz new file mode 100755 index 0000000..9c90b18 Binary files /dev/null and b/gocryptfs.gz differ diff --git a/internal/speed/speed.go b/internal/speed/speed.go new file mode 100644 index 0000000..f9bf93c --- /dev/null +++ b/internal/speed/speed.go @@ -0,0 +1,120 @@ +// Package speed implements the "-speed" command-line option, +// similar to "openssl speed". +// It benchmarks the crypto algorithms and libraries used by +// gocryptfs. +package speed + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "fmt" + "log" + "testing" + + "github.com/rfjakob/gocryptfs/internal/prefer_openssl" + "github.com/rfjakob/gocryptfs/internal/siv_aead" + "github.com/rfjakob/gocryptfs/internal/stupidgcm" +) + +func Run() { + bTable := []struct { + name string + f func(*testing.B) + preferred bool + }{ + {name: "AES-GCM-256-OpenSSL", f: bStupidGCM, preferred: prefer_openssl.PreferOpenSSL()}, + {name: "AES-GCM-256-Go", f: bGoGCM, preferred: !prefer_openssl.PreferOpenSSL()}, + {name: "AES-SIV-512-Go", f: bAESSIV, preferred: false}, + } + for _, b := range bTable { + fmt.Printf("%-20s\t", b.name) + mbs := mbPerSec(testing.Benchmark(b.f)) + if mbs > 0 { + fmt.Printf("%7.2f MB/s", mbs) + } else { + fmt.Printf(" N/A") + } + if b.preferred { + fmt.Printf("\t(selected in auto mode)\n") + } else { + fmt.Printf("\t\n") + } + } +} + +func mbPerSec(r testing.BenchmarkResult) float64 { + if r.Bytes <= 0 || r.T <= 0 || r.N <= 0 { + return 0 + } + return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds() +} + +// Get "n" random bytes from /dev/urandom or panic +func randBytes(n int) []byte { + b := make([]byte, n) + _, err := rand.Read(b) + if err != nil { + log.Panic("Failed to read random bytes: " + err.Error()) + } + return b +} + +const blockSize = 4096 + +func bStupidGCM(b *testing.B) { + if stupidgcm.BuiltWithoutOpenssl { + b.Skip("openssl has been disabled at compile-time") + } + key := randBytes(32) + authData := randBytes(24) + iv := randBytes(16) + in := make([]byte, blockSize) + b.SetBytes(int64(len(in))) + + sGCM := stupidgcm.New(key) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Encrypt and append to nonce + sGCM.Seal(iv, iv, in, authData) + } +} + +func bGoGCM(b *testing.B) { + key := randBytes(32) + authData := randBytes(24) + iv := randBytes(16) + in := make([]byte, blockSize) + b.SetBytes(int64(len(in))) + + gAES, err := aes.NewCipher(key) + if err != nil { + b.Fatal(err) + } + gGCM, err := cipher.NewGCMWithNonceSize(gAES, 16) + if err != nil { + b.Fatal(err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Encrypt and append to nonce + gGCM.Seal(iv, iv, in, authData) + } +} + +func bAESSIV(b *testing.B) { + key := randBytes(64) + authData := randBytes(24) + iv := randBytes(16) + in := make([]byte, blockSize) + b.SetBytes(int64(len(in))) + gGCM := siv_aead.New(key) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + // Encrypt and append to nonce + gGCM.Seal(iv, iv, in, authData) + } +} diff --git a/internal/speed/speed_test.go b/internal/speed/speed_test.go new file mode 100644 index 0000000..1e9d859 --- /dev/null +++ b/internal/speed/speed_test.go @@ -0,0 +1,29 @@ +package speed + +/* +Make the "-speed" benchmarks also accessible to the standard test system. +Example run: + +$ go test -bench . +BenchmarkStupidGCM-2 100000 22552 ns/op 181.62 MB/s +BenchmarkGoGCM-2 20000 81871 ns/op 50.03 MB/s +BenchmarkAESSIV-2 10000 104623 ns/op 39.15 MB/s +PASS +ok github.com/rfjakob/gocryptfs/internal/speed 6.022s +*/ + +import ( + "testing" +) + +func BenchmarkStupidGCM(b *testing.B) { + bStupidGCM(b) +} + +func BenchmarkGoGCM(b *testing.B) { + bGoGCM(b) +} + +func BenchmarkAESSIV(b *testing.B) { + bAESSIV(b) +} diff --git a/internal/stupidgcm/stupidgcm_test.go b/internal/stupidgcm/stupidgcm_test.go index d6c0714..ba25855 100644 --- a/internal/stupidgcm/stupidgcm_test.go +++ b/internal/stupidgcm/stupidgcm_test.go @@ -15,9 +15,6 @@ import ( "encoding/hex" "log" "testing" - - // For benchmark comparison - "github.com/rfjakob/gocryptfs/internal/siv_aead" ) // Get "n" random bytes from /dev/urandom or panic @@ -121,58 +118,3 @@ func TestCorruption(t *testing.T) { t.Fatalf("Should have gotten error") } } - -// $ go test -bench . -// PASS -// Benchmark4kEncStupidGCM-2 50000 25622 ns/op 159.86 MB/s -// Benchmark4kEncGoGCM-2 10000 116544 ns/op 35.15 MB/s -// ok github.com/rfjakob/gocryptfs/internal/stupidgcm 3.775s -func Benchmark4kEncStupidGCM(b *testing.B) { - key := randBytes(32) - authData := randBytes(24) - iv := randBytes(16) - in := make([]byte, 4096) - b.SetBytes(int64(len(in))) - - sGCM := New(key) - - for i := 0; i < b.N; i++ { - // Encrypt and append to nonce - sGCM.Seal(iv, iv, in, authData) - } -} - -func Benchmark4kEncGoGCM(b *testing.B) { - key := randBytes(32) - authData := randBytes(24) - iv := randBytes(16) - in := make([]byte, 4096) - b.SetBytes(int64(len(in))) - - gAES, err := aes.NewCipher(key) - if err != nil { - b.Fatal(err) - } - gGCM, err := cipher.NewGCMWithNonceSize(gAES, 16) - if err != nil { - b.Fatal(err) - } - - for i := 0; i < b.N; i++ { - // Encrypt and append to nonce - gGCM.Seal(iv, iv, in, authData) - } -} - -func Benchmark4kEncAESSIV(b *testing.B) { - key := randBytes(32) - authData := randBytes(24) - iv := randBytes(16) - in := make([]byte, 4096) - b.SetBytes(int64(len(in))) - gGCM := siv_aead.New(key) - for i := 0; i < b.N; i++ { - // Encrypt and append to nonce - gGCM.Seal(iv, iv, in, authData) - } -} diff --git a/main.go b/main.go index 21f2d06..9301ae1 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "github.com/rfjakob/gocryptfs/internal/configfile" "github.com/rfjakob/gocryptfs/internal/contentenc" "github.com/rfjakob/gocryptfs/internal/readpassword" + "github.com/rfjakob/gocryptfs/internal/speed" "github.com/rfjakob/gocryptfs/internal/stupidgcm" "github.com/rfjakob/gocryptfs/internal/tlog" ) @@ -149,6 +150,11 @@ func main() { printVersion() os.Exit(0) } + // "-speed" + if args.speed { + speed.Run() + os.Exit(0) + } if args.wpanic { tlog.Warn.Wpanic = true tlog.Debug.Printf("Panicing on warnings")