From 39f3a24484ffc343d2d5225d5c419082aabb9baa Mon Sep 17 00:00:00 2001 From: Jakob Unterwurzacher Date: Wed, 4 May 2016 19:09:14 +0200 Subject: [PATCH] stupidgcm: completely replace spacemonkeygo/openssl --- Documentation/openssl-gcm.md | 13 ++ internal/cryptocore/cryptocore.go | 7 +- internal/cryptocore/openssl_aead.go | 100 ------------- internal/cryptocore/openssl_benchmark.bash | 3 - internal/cryptocore/openssl_test.go | 75 ---------- openssl_benchmark/go-vs-openssl.md | 90 ------------ openssl_benchmark/openssl_benchmark.bash | 5 - openssl_benchmark/openssl_test.go | 163 --------------------- 8 files changed, 18 insertions(+), 438 deletions(-) create mode 100644 Documentation/openssl-gcm.md delete mode 100644 internal/cryptocore/openssl_aead.go delete mode 100755 internal/cryptocore/openssl_benchmark.bash delete mode 100644 internal/cryptocore/openssl_test.go delete mode 100644 openssl_benchmark/go-vs-openssl.md delete mode 100755 openssl_benchmark/openssl_benchmark.bash delete mode 100644 openssl_benchmark/openssl_test.go diff --git a/Documentation/openssl-gcm.md b/Documentation/openssl-gcm.md new file mode 100644 index 0000000..d955b39 --- /dev/null +++ b/Documentation/openssl-gcm.md @@ -0,0 +1,13 @@ +Go builtin GCM vs OpenSSL +========================= + +OpenSSL is over four times faster than Go's built-in GCM implementation. + +``` +$ cd internal/stupidgcm +$ go test -bench . +PASS +Benchmark4kEncStupidGCM-2 50000 25860 ns/op 158.39 MB/s +Benchmark4kEncGoGCM-2 10000 116050 ns/op 35.29 MB/s +ok github.com/rfjakob/gocryptfs/internal/stupidgcm 3.667s +``` diff --git a/internal/cryptocore/cryptocore.go b/internal/cryptocore/cryptocore.go index f286896..7e7f099 100644 --- a/internal/cryptocore/cryptocore.go +++ b/internal/cryptocore/cryptocore.go @@ -4,6 +4,8 @@ import ( "crypto/aes" "crypto/cipher" "fmt" + + "github.com/rfjakob/gocryptfs/internal/stupidgcm" ) const ( @@ -39,8 +41,9 @@ func New(key []byte, useOpenssl bool, GCMIV128 bool) *CryptoCore { } var gcm cipher.AEAD - if useOpenssl { - gcm = opensslGCM{key} + if useOpenssl && GCMIV128 { + // stupidgcm only supports 128-bit IVs + gcm = stupidgcm.New(key) } else { gcm, err = goGCMWrapper(blockCipher, IVLen) if err != nil { diff --git a/internal/cryptocore/openssl_aead.go b/internal/cryptocore/openssl_aead.go deleted file mode 100644 index d4ed64b..0000000 --- a/internal/cryptocore/openssl_aead.go +++ /dev/null @@ -1,100 +0,0 @@ -package cryptocore - -// Implements cipher.AEAD with OpenSSL backend - -import ( - "bytes" - "github.com/spacemonkeygo/openssl" -) - -// Supports all nonce sizes -type opensslGCM struct { - key []byte -} - -func (be opensslGCM) Overhead() int { - return AuthTagLen -} - -func (be opensslGCM) NonceSize() int { - // We support any nonce size - return -1 -} - -// Seal encrypts and authenticates plaintext, authenticates the -// additional data and appends the result to dst, returning the updated -// slice. opensslGCM supports any nonce size. -func (be opensslGCM) Seal(dst, nonce, plaintext, data []byte) []byte { - - // Preallocate output buffer - var cipherBuf bytes.Buffer - cipherBuf.Grow(len(dst) + len(plaintext) + AuthTagLen) - // Output will be appended to dst - cipherBuf.Write(dst) - - ectx, err := openssl.NewGCMEncryptionCipherCtx(KeyLen*8, nil, be.key, nonce) - if err != nil { - panic(err) - } - err = ectx.ExtraData(data) - if err != nil { - panic(err) - } - part, err := ectx.EncryptUpdate(plaintext) - if err != nil { - panic(err) - } - cipherBuf.Write(part) - part, err = ectx.EncryptFinal() - if err != nil { - panic(err) - } - cipherBuf.Write(part) - part, err = ectx.GetTag() - if err != nil { - panic(err) - } - cipherBuf.Write(part) - - return cipherBuf.Bytes() -} - -// Open decrypts and authenticates ciphertext, authenticates the -// additional data and, if successful, appends the resulting plaintext -// to dst, returning the updated slice. The nonce must be NonceSize() -// bytes long and both it and the additional data must match the -// value passed to Seal. -// -// The ciphertext and dst may alias exactly or not at all. -func (be opensslGCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - - l := len(ciphertext) - tag := ciphertext[l-AuthTagLen : l] - ciphertext = ciphertext[0 : l-AuthTagLen] - plainBuf := bytes.NewBuffer(dst) - - dctx, err := openssl.NewGCMDecryptionCipherCtx(KeyLen*8, nil, be.key, nonce) - if err != nil { - return nil, err - } - err = dctx.ExtraData(data) - if err != nil { - return nil, err - } - part, err := dctx.DecryptUpdate(ciphertext) - if err != nil { - return nil, err - } - plainBuf.Write(part) - err = dctx.SetTag(tag) - if err != nil { - return nil, err - } - part, err = dctx.DecryptFinal() - if err != nil { - return nil, err - } - plainBuf.Write(part) - - return plainBuf.Bytes(), nil -} diff --git a/internal/cryptocore/openssl_benchmark.bash b/internal/cryptocore/openssl_benchmark.bash deleted file mode 100755 index df29628..0000000 --- a/internal/cryptocore/openssl_benchmark.bash +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -go test -run NONE -bench BenchmarkEnc diff --git a/internal/cryptocore/openssl_test.go b/internal/cryptocore/openssl_test.go deleted file mode 100644 index 94b696a..0000000 --- a/internal/cryptocore/openssl_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package cryptocore - -// Benchmark go built-int GCM against spacemonkey openssl bindings -// -// Note: The benchmarks in this file supersede the ones in the openssl_benchmark -// directory as they use the same code paths that gocryptfs actually uses. -// -// Run benchmark: -// go test -bench Enc - -import ( - "crypto/aes" - "testing" -) - -func benchmarkGoEnc(b *testing.B, plaintext []byte, key []byte, nonce []byte) (ciphertext []byte) { - b.SetBytes(int64(len(plaintext))) - aes, err := aes.NewCipher(key[:]) - if err != nil { - b.Fatal(err) - } - aesgcm, err := goGCMWrapper(aes, len(nonce)) - if err != nil { - b.Fatal(err) - } - // This would be fileID + blockNo - aData := make([]byte, 24) - b.ResetTimer() - for i := 0; i < b.N; i++ { - // Encrypt plaintext and append to nonce - ciphertext = aesgcm.Seal(nonce, nonce, plaintext, aData) - } - return ciphertext -} - -func benchmarkOpensslEnc(b *testing.B, plaintext []byte, key []byte, nonce []byte) (ciphertext []byte) { - b.SetBytes(int64(len(plaintext))) - var aesgcm opensslGCM - aesgcm.key = key - // This would be fileID + blockNo - aData := make([]byte, 24) - for i := 0; i < b.N; i++ { - // Encrypt plaintext and append to nonce - ciphertext = aesgcm.Seal(nonce, nonce, plaintext, aData) - } - return ciphertext -} - -func BenchmarkEnc_Go_4k_AES256_nonce96(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 96/8) - benchmarkGoEnc(b, plaintext, key, nonce) -} - -func BenchmarkEnc_Go_4k_AES256_nonce128(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 128/8) - benchmarkGoEnc(b, plaintext, key, nonce) -} - -func BenchmarkEnc_OpenSSL_4k_AES256_nonce96(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 96/8) - benchmarkOpensslEnc(b, plaintext, key, nonce) -} - -func BenchmarkEnc_OpenSSL_4k_AES256_nonce128(b *testing.B) { - plaintext := make([]byte, 4048) - key := make([]byte, 256/8) - nonce := make([]byte, 96/8) - benchmarkOpensslEnc(b, plaintext, key, nonce) -} diff --git a/openssl_benchmark/go-vs-openssl.md b/openssl_benchmark/go-vs-openssl.md deleted file mode 100644 index 222ae8e..0000000 --- a/openssl_benchmark/go-vs-openssl.md +++ /dev/null @@ -1,90 +0,0 @@ -Go 1.4.2 -======== - -39MB/s @1k - - go1.4/src/crypto/cipher$ go test -bench=. - - BenchmarkAESGCMSeal1K 50000 25968 ns/op 39.43 MB/s - BenchmarkAESGCMOpen1K 50000 25914 ns/op 39.51 MB/s - [...] - -Go 1.5 -====== - -41MB/s @1k - - go1.5/src/crypto/cipher$ ~/go/src/go1.5/bin/go test -bench=. - - BenchmarkAESGCMSeal1K-2 50000 24429 ns/op 41.92 MB/s - BenchmarkAESGCMOpen1K-2 50000 24578 ns/op 41.66 MB/s - BenchmarkAESGCMSeal8K-2 10000 190340 ns/op 43.04 MB/s - BenchmarkAESGCMOpen8K-2 10000 190308 ns/op 43.05 MB/s - [...] - -openssl 1.0.1k -============== - -302MB/s @1k - - $ openssl speed -elapsed -evp aes-128-gcm - - [...] - The 'numbers' are in 1000s of bytes per second processed. - type 16 bytes 64 bytes 256 bytes 1024 bytes 8192 bytes - aes-128-gcm 71275.15k 80063.19k 275048.36k 302066.69k 308912.13k - - -gocryptfs with openssl bindings -=============================== - -148MB/s @4k - - gocryptfs/openssl_benchmark$ ./openssl_benchmark.bash - - BenchmarkAESGCMSeal4K 20000 98671 ns/op 41.51 MB/s - BenchmarkAESGCMOpen4K 20000 98679 ns/op 41.51 MB/s - BenchmarkOpensslGCMenc4K 50000 27542 ns/op 148.72 MB/s - BenchmarkOpensslGCMdec4K 50000 27564 ns/op 148.60 MB/s - - -CPU Info -======== - -This is tested on a dual-core Intel Sandy Bridge Pentium G630 which does NOT have -aes instructions ( https://en.wikipedia.org/wiki/AES_instruction_set ) - - $ cat /proc/cpuinfo | fold -s -w 80 - - processor : 0 - vendor_id : GenuineIntel - cpu family : 6 - model : 42 - model name : Intel(R) Pentium(R) CPU G630 @ 2.70GHz - stepping : 7 - microcode : 0x29 - cpu MHz : 1617.574 - cache size : 3072 KB - physical id : 0 - siblings : 2 - core id : 0 - cpu cores : 2 - apicid : 0 - initial apicid : 0 - fpu : yes - fpu_exception : yes - cpuid level : 13 - wp : yes - flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov - pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm - constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc - aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 - xtpr pdcm pcid sse4_1 sse4_2 popcnt tsc_deadline_timer xsave lahf_lm arat epb - pln pts dtherm tpr_shadow vnmi flexpriority ept vpid xsaveopt - bugs : - bogomips : 5387.68 - clflush size : 64 - cache_alignment : 64 - address sizes : 36 bits physical, 48 bits virtual - power management: - [...] diff --git a/openssl_benchmark/openssl_benchmark.bash b/openssl_benchmark/openssl_benchmark.bash deleted file mode 100755 index b328d24..0000000 --- a/openssl_benchmark/openssl_benchmark.bash +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash - -set -eux - -go test -bench=. diff --git a/openssl_benchmark/openssl_test.go b/openssl_benchmark/openssl_test.go deleted file mode 100644 index 1530f9b..0000000 --- a/openssl_benchmark/openssl_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package benchmark - -// Benchmark go built-int GCM against spacemonkey openssl bindings -// -// Note: This is deprecated in favor of the benchmarks integrated in cryptfs. -// -// Run benchmark: -// go test -bench=. - -import ( - "bytes" - "crypto/aes" - "crypto/cipher" - "fmt" - "os" - "testing" - - "github.com/spacemonkeygo/openssl" - - "github.com/rfjakob/gocryptfs/internal/cryptocore" -) - -func TestMain(m *testing.M) { - - fmt.Printf("Benchmarking AES-GCM-%d with 4kB block size\n", cryptocore.KeyLen*8) - - r := m.Run() - os.Exit(r) -} - -func BenchmarkGoEnc4K(b *testing.B) { - buf := make([]byte, 1024*4) - b.SetBytes(int64(len(buf))) - - var key [cryptocore.KeyLen]byte - var nonce [12]byte - aes, _ := aes.NewCipher(key[:]) - aesgcm, _ := cipher.NewGCM(aes) - var out []byte - // This would be fileID + blockNo - aData := make([]byte, 24) - b.ResetTimer() - for i := 0; i < b.N; i++ { - out = aesgcm.Seal(out[:0], nonce[:], buf, aData) - } -} - -func BenchmarkGoDec4K(b *testing.B) { - buf := make([]byte, 1024*4) - b.SetBytes(int64(len(buf))) - - var key [cryptocore.KeyLen]byte - var nonce [12]byte - aes, _ := aes.NewCipher(key[:]) - aesgcm, _ := cipher.NewGCM(aes) - var out []byte - out = aesgcm.Seal(out[:0], nonce[:], buf, nil) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := aesgcm.Open(buf[:0], nonce[:], out, nil) - if err != nil { - b.Errorf("Open: %v", err) - } - } -} - -func BenchmarkOpensslEnc4K(b *testing.B) { - buf := make([]byte, 1024*4) - b.SetBytes(int64(len(buf))) - - var key [cryptocore.KeyLen]byte - var nonce [12]byte - - // This would be fileID + blockNo - aData := make([]byte, 24) - - var ciphertext bytes.Buffer - var part []byte - - b.ResetTimer() - for i := 0; i < b.N; i++ { - ciphertext.Reset() - ectx, err := openssl.NewGCMEncryptionCipherCtx(cryptocore.KeyLen*8, nil, key[:], nonce[:]) - if err != nil { - b.FailNow() - } - err = ectx.ExtraData(aData) - if err != nil { - b.FailNow() - } - part, err = ectx.EncryptUpdate(buf) - if err != nil { - b.FailNow() - } - ciphertext.Write(part) - part, err = ectx.EncryptFinal() - if err != nil { - b.FailNow() - } - ciphertext.Write(part) - part, err = ectx.GetTag() - if err != nil { - b.FailNow() - } - ciphertext.Write(part) - } -} - -func BenchmarkOpensslDec4K(b *testing.B) { - buf := makeOpensslCiphertext() - b.SetBytes(int64(1024 * 4)) - - tag := buf[4096:] - buf = buf[0:4096] - - var key [cryptocore.KeyLen]byte - var nonce [12]byte - - var plaintext bytes.Buffer - var part []byte - - b.ResetTimer() - for i := 0; i < b.N; i++ { - plaintext.Reset() - dctx, err := openssl.NewGCMDecryptionCipherCtx(cryptocore.KeyLen*8, nil, key[:], nonce[:]) - if err != nil { - b.FailNow() - } - part, err = dctx.DecryptUpdate(buf) - if err != nil { - b.FailNow() - } - plaintext.Write(part) - err = dctx.SetTag(tag) - if err != nil { - b.FailNow() - } - part, err = dctx.DecryptFinal() - if err != nil { - b.FailNow() - } - plaintext.Write(part) - } -} - -func makeOpensslCiphertext() []byte { - buf := make([]byte, 1024*4) - var key [cryptocore.KeyLen]byte - var nonce [12]byte - var ciphertext bytes.Buffer - var part []byte - - ectx, _ := openssl.NewGCMEncryptionCipherCtx(cryptocore.KeyLen*8, nil, key[:], nonce[:]) - part, _ = ectx.EncryptUpdate(buf) - ciphertext.Write(part) - part, _ = ectx.EncryptFinal() - ciphertext.Write(part) - part, _ = ectx.GetTag() - ciphertext.Write(part) - - return ciphertext.Bytes() -}