stupidgcm: completely replace spacemonkeygo/openssl
This commit is contained in:
parent
c92190bf07
commit
39f3a24484
13
Documentation/openssl-gcm.md
Normal file
13
Documentation/openssl-gcm.md
Normal 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
|
||||||
|
```
|
@ -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 {
|
||||||
|
@ -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
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
go test -run NONE -bench BenchmarkEnc
|
|
@ -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)
|
|
||||||
}
|
|
@ -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:
|
|
||||||
[...]
|
|
@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -eux
|
|
||||||
|
|
||||||
go test -bench=.
|
|
@ -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()
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user