stupidgcm: completely replace spacemonkeygo/openssl

This commit is contained in:
Jakob Unterwurzacher 2016-05-04 19:09:14 +02:00
parent c92190bf07
commit 39f3a24484
8 changed files with 18 additions and 438 deletions

View File

@ -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
```

View File

@ -4,6 +4,8 @@ import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"fmt" "fmt"
"github.com/rfjakob/gocryptfs/internal/stupidgcm"
) )
const ( const (
@ -39,8 +41,9 @@ func New(key []byte, useOpenssl bool, GCMIV128 bool) *CryptoCore {
} }
var gcm cipher.AEAD var gcm cipher.AEAD
if useOpenssl { if useOpenssl && GCMIV128 {
gcm = opensslGCM{key} // stupidgcm only supports 128-bit IVs
gcm = stupidgcm.New(key)
} else { } else {
gcm, err = goGCMWrapper(blockCipher, IVLen) gcm, err = goGCMWrapper(blockCipher, IVLen)
if err != nil { if err != nil {

View File

@ -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
}

View File

@ -1,3 +0,0 @@
#!/bin/bash
go test -run NONE -bench BenchmarkEnc

View File

@ -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)
}

View File

@ -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:
[...]

View File

@ -1,5 +0,0 @@
#!/bin/bash
set -eux
go test -bench=.

View File

@ -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()
}