diff options
author | Jakob Unterwurzacher | 2021-09-04 11:41:56 +0200 |
---|---|---|
committer | Jakob Unterwurzacher | 2021-09-07 18:14:05 +0200 |
commit | e2ec048a09889b2bf71e8bbfef9f0584ff7d69db (patch) | |
tree | 84bb1f8c709f8db3b2dd551c7c5343c0ffe44ed9 /internal/stupidgcm | |
parent | bf572aef88963732849b8e5ae679e63c6be4aa46 (diff) |
stupidgcm: introduce stupidAEADCommon and use for both chacha & gcm
Nice deduplication and brings the GCM decrypt speed up to par.
internal/speed$ benchstat old new
name old time/op new time/op delta
StupidGCM-4 4.71µs ± 0% 4.66µs ± 0% -0.99% (p=0.008 n=5+5)
StupidGCMDecrypt-4 5.77µs ± 1% 4.51µs ± 0% -21.80% (p=0.008 n=5+5)
name old speed new speed delta
StupidGCM-4 870MB/s ± 0% 879MB/s ± 0% +1.01% (p=0.008 n=5+5)
StupidGCMDecrypt-4 710MB/s ± 1% 908MB/s ± 0% +27.87% (p=0.008 n=5+5)
Diffstat (limited to 'internal/stupidgcm')
-rw-r--r-- | internal/stupidgcm/chacha.go | 35 | ||||
-rw-r--r-- | internal/stupidgcm/chacha_test.go (renamed from internal/stupidgcm/stupidchacha_test.go) | 0 | ||||
-rw-r--r-- | internal/stupidgcm/common.go | 68 | ||||
-rw-r--r-- | internal/stupidgcm/common_test.go | 17 | ||||
-rw-r--r-- | internal/stupidgcm/gcm.go | 45 | ||||
-rw-r--r-- | internal/stupidgcm/gcm_test.go (renamed from internal/stupidgcm/stupidgcm_test.go) | 0 | ||||
-rw-r--r-- | internal/stupidgcm/openssl.go | 108 | ||||
-rw-r--r-- | internal/stupidgcm/openssl_aead.c (renamed from internal/stupidgcm/chacha.c) | 26 | ||||
-rw-r--r-- | internal/stupidgcm/openssl_aead.h (renamed from internal/stupidgcm/chacha.h) | 13 | ||||
-rw-r--r-- | internal/stupidgcm/stupidchacha.go | 156 | ||||
-rw-r--r-- | internal/stupidgcm/stupidgcm.go | 204 | ||||
-rw-r--r-- | internal/stupidgcm/xchacha.go (renamed from internal/stupidgcm/stupidxchacha.go) | 10 | ||||
-rw-r--r-- | internal/stupidgcm/xchacha_test.go (renamed from internal/stupidgcm/stupidxchacha_test.go) | 0 |
13 files changed, 280 insertions, 402 deletions
diff --git a/internal/stupidgcm/chacha.go b/internal/stupidgcm/chacha.go new file mode 100644 index 0000000..37f7e1f --- /dev/null +++ b/internal/stupidgcm/chacha.go @@ -0,0 +1,35 @@ +// +build !without_openssl + +package stupidgcm + +import ( + "crypto/cipher" + "log" + + "golang.org/x/crypto/chacha20poly1305" +) + +/* +#include <openssl/evp.h> +*/ +import "C" + +type stupidChacha20poly1305 struct { + stupidAEADCommon +} + +// Verify that we satisfy the cipher.AEAD interface +var _ cipher.AEAD = &stupidChacha20poly1305{} + +func newChacha20poly1305(key []byte) *stupidChacha20poly1305 { + if len(key) != chacha20poly1305.KeySize { + log.Panicf("Only %d-byte keys are supported, you passed %d bytes", chacha20poly1305.KeySize, len(key)) + } + return &stupidChacha20poly1305{ + stupidAEADCommon{ + key: append([]byte{}, key...), // private copy + openSSLEVPCipher: C.EVP_chacha20_poly1305(), + nonceSize: chacha20poly1305.NonceSize, + }, + } +} diff --git a/internal/stupidgcm/stupidchacha_test.go b/internal/stupidgcm/chacha_test.go index 513b68f..513b68f 100644 --- a/internal/stupidgcm/stupidchacha_test.go +++ b/internal/stupidgcm/chacha_test.go diff --git a/internal/stupidgcm/common.go b/internal/stupidgcm/common.go new file mode 100644 index 0000000..3788315 --- /dev/null +++ b/internal/stupidgcm/common.go @@ -0,0 +1,68 @@ +package stupidgcm + +import ( + "log" +) + +/* +#include <openssl/evp.h> +*/ +import "C" + +type stupidAEADCommon struct { + wiped bool + key []byte + openSSLEVPCipher *C.EVP_CIPHER + nonceSize int +} + +// Overhead returns the number of bytes that are added for authentication. +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) Overhead() int { + return tagLen +} + +// NonceSize returns the required size of the nonce / IV +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) NonceSize() int { + return c.nonceSize +} + +// Seal encrypts "in" using "iv" and "authData" and append the result to "dst" +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) Seal(dst, iv, in, authData []byte) []byte { + return openSSLSeal(c, dst, iv, in, authData) +} + +// Open decrypts "in" using "iv" and "authData" and append the result to "dst" +// +// Part of the cipher.AEAD interface. +func (c *stupidAEADCommon) Open(dst, iv, in, authData []byte) ([]byte, error) { + return openSSLOpen(c, dst, iv, in, authData) +} + +// Wipe tries to wipe the key from memory by overwriting it with zeros. +// +// This is not bulletproof due to possible GC copies, but +// still raises the bar for extracting the key. +func (c *stupidAEADCommon) Wipe() { + key := c.key + c.wiped = true + c.key = nil + for i := range key { + key[i] = 0 + } +} + +func (c *stupidAEADCommon) Wiped() bool { + if c.wiped { + return true + } + if len(c.key) != keyLen { + log.Panicf("wrong key length %d", len(c.key)) + } + return false +} diff --git a/internal/stupidgcm/common_test.go b/internal/stupidgcm/common_test.go index 8123ce2..a8080ca 100644 --- a/internal/stupidgcm/common_test.go +++ b/internal/stupidgcm/common_test.go @@ -162,25 +162,26 @@ func testCorruption(t *testing.T, c cipher.AEAD) { } } -type Wiper interface { - Wipe() -} - func testWipe(t *testing.T, c cipher.AEAD) { switch c2 := c.(type) { case *StupidGCM: c2.Wipe() - if c2.key != nil { - t.Fatal("key is not nil") + if !c2.Wiped() { + t.Error("c2.wiped is not set") + } + for _, v := range c2.key { + if v != 0 { + t.Fatal("c2._key is not zeroed") + } } case *stupidChacha20poly1305: c2.Wipe() - if !c2.wiped { + if !c2.Wiped() { t.Error("c2.wiped is not set") } for _, v := range c2.key { if v != 0 { - t.Fatal("c2.key is not zeroed") + t.Fatal("c2._key is not zeroed") } } case *stupidXchacha20poly1305: diff --git a/internal/stupidgcm/gcm.go b/internal/stupidgcm/gcm.go new file mode 100644 index 0000000..439e7a7 --- /dev/null +++ b/internal/stupidgcm/gcm.go @@ -0,0 +1,45 @@ +// +build !without_openssl + +// Package stupidgcm is a thin wrapper for OpenSSL's GCM encryption and +// decryption functions. It only support 32-byte keys and 16-bit IVs. +package stupidgcm + +// #include <openssl/evp.h> +import "C" + +import ( + "crypto/cipher" + "log" +) + +const ( + // BuiltWithoutOpenssl indicates if openssl been disabled at compile-time + BuiltWithoutOpenssl = false + + keyLen = 32 + ivLen = 16 + tagLen = 16 +) + +// StupidGCM implements the cipher.AEAD interface +type StupidGCM struct { + stupidAEADCommon +} + +// Verify that we satisfy the interface +var _ cipher.AEAD = &StupidGCM{} + +// New returns a new cipher.AEAD implementation.. +func New(keyIn []byte, forceDecode bool) cipher.AEAD { + if len(keyIn) != keyLen { + log.Panicf("Only %d-byte keys are supported", keyLen) + } + return &StupidGCM{ + stupidAEADCommon{ + // Create a private copy of the key + key: append([]byte{}, keyIn...), + openSSLEVPCipher: C.EVP_aes_256_gcm(), + nonceSize: ivLen, + }, + } +} diff --git a/internal/stupidgcm/stupidgcm_test.go b/internal/stupidgcm/gcm_test.go index 5323afa..5323afa 100644 --- a/internal/stupidgcm/stupidgcm_test.go +++ b/internal/stupidgcm/gcm_test.go diff --git a/internal/stupidgcm/openssl.go b/internal/stupidgcm/openssl.go new file mode 100644 index 0000000..d57d100 --- /dev/null +++ b/internal/stupidgcm/openssl.go @@ -0,0 +1,108 @@ +package stupidgcm + +import ( + "fmt" + "log" +) + +/* +#include "openssl_aead.h" +#cgo pkg-config: libcrypto +*/ +import "C" + +func openSSLSeal(a *stupidAEADCommon, dst, iv, in, authData []byte) []byte { + if a.Wiped() { + panic("BUG: tried to use wiped key") + } + if len(iv) != a.NonceSize() { + log.Panicf("Only %d-byte IVs are supported, you passed %d bytes", a.NonceSize(), len(iv)) + } + if len(in) == 0 { + log.Panic("Zero-length input data is not supported") + } + + // If the "dst" slice is large enough we can use it as our output buffer + outLen := len(in) + tagLen + var buf []byte + inplace := false + if cap(dst)-len(dst) >= outLen { + inplace = true + buf = dst[len(dst) : len(dst)+outLen] + } else { + buf = make([]byte, outLen) + } + + res := int(C.openssl_aead_seal(a.openSSLEVPCipher, + (*C.uchar)(&in[0]), + C.int(len(in)), + (*C.uchar)(&authData[0]), + C.int(len(authData)), + (*C.uchar)(&a.key[0]), + C.int(len(a.key)), + (*C.uchar)(&iv[0]), + C.int(len(iv)), + (*C.uchar)(&buf[0]), + C.int(len(buf)))) + + if res != outLen { + log.Panicf("expected length %d, got %d", outLen, res) + } + + if inplace { + return dst[:len(dst)+outLen] + } + return append(dst, buf...) +} + +func openSSLOpen(a *stupidAEADCommon, dst, iv, in, authData []byte) ([]byte, error) { + if a.Wiped() { + panic("BUG: tried to use wiped key") + } + if len(iv) != a.NonceSize() { + log.Panicf("Only %d-byte IVs are supported, you passed %d bytes", a.NonceSize(), len(iv)) + } + if len(in) <= tagLen { + return nil, fmt.Errorf("stupidChacha20poly1305: input data too short (%d bytes)", len(in)) + } + + // If the "dst" slice is large enough we can use it as our output buffer + outLen := len(in) - tagLen + var buf []byte + inplace := false + if cap(dst)-len(dst) >= outLen { + inplace = true + buf = dst[len(dst) : len(dst)+outLen] + } else { + buf = make([]byte, len(in)-tagLen) + } + + ciphertext := in[:len(in)-tagLen] + tag := in[len(in)-tagLen:] + + res := int(C.openssl_aead_open(a.openSSLEVPCipher, + (*C.uchar)(&ciphertext[0]), + C.int(len(ciphertext)), + (*C.uchar)(&authData[0]), + C.int(len(authData)), + (*C.uchar)(&tag[0]), + C.int(len(tag)), + (*C.uchar)(&a.key[0]), + C.int(len(a.key)), + (*C.uchar)(&iv[0]), + C.int(len(iv)), + (*C.uchar)(&buf[0]), + C.int(len(buf)))) + + if res < 0 { + return nil, ErrAuth + } + if res != outLen { + log.Panicf("unexpected length %d", res) + } + + if inplace { + return dst[:len(dst)+outLen], nil + } + return append(dst, buf...), nil +} diff --git a/internal/stupidgcm/chacha.c b/internal/stupidgcm/openssl_aead.c index 05d68af..9dc6866 100644 --- a/internal/stupidgcm/chacha.c +++ b/internal/stupidgcm/openssl_aead.c @@ -1,4 +1,4 @@ -#include "chacha.h" +#include "openssl_aead.h" #include <openssl/evp.h> #include <stdio.h> //#cgo pkg-config: libcrypto @@ -9,24 +9,12 @@ static void panic(const char* const msg) __builtin_trap(); } -static const EVP_CIPHER* getEvpCipher(enum aeadType cipherId) -{ - switch (cipherId) { - case aeadTypeChacha: - return EVP_chacha20_poly1305(); - case aeadTypeGcm: - return EVP_aes_256_gcm(); - } - panic("unknown cipherId"); - return NULL; -} - // We only support 16-byte tags static const int supportedTagLen = 16; // https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_GCM_mode -int aead_seal( - const enum aeadType cipherId, +int openssl_aead_seal( + const EVP_CIPHER* evpCipher, const unsigned char* const plaintext, const int plaintextLen, const unsigned char* const authData, @@ -38,8 +26,6 @@ int aead_seal( unsigned char* const ciphertext, const int ciphertextBufLen) { - const EVP_CIPHER* evpCipher = getEvpCipher(cipherId); - // Create scratch space "ctx" EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) { @@ -111,8 +97,8 @@ int aead_seal( return ciphertextLen; } -int aead_open( - const enum aeadType cipherId, +int openssl_aead_open( + const EVP_CIPHER* evpCipher, const unsigned char* const ciphertext, const int ciphertextLen, const unsigned char* const authData, @@ -126,8 +112,6 @@ int aead_open( unsigned char* const plaintext, const int plaintextBufLen) { - const EVP_CIPHER* evpCipher = getEvpCipher(cipherId); - // Create scratch space "ctx" EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); if (!ctx) { diff --git a/internal/stupidgcm/chacha.h b/internal/stupidgcm/openssl_aead.h index a5eac04..6a818b6 100644 --- a/internal/stupidgcm/chacha.h +++ b/internal/stupidgcm/openssl_aead.h @@ -1,10 +1,7 @@ -enum aeadType { - aeadTypeChacha = 1, - aeadTypeGcm = 2, -}; +#include <openssl/evp.h> -int aead_seal( - const enum aeadType cipherId, +int openssl_aead_seal( + const EVP_CIPHER* evpCipher, const unsigned char* const plaintext, const int plaintextLen, const unsigned char* const authData, @@ -16,8 +13,8 @@ int aead_seal( unsigned char* const ciphertext, const int ciphertextBufLen); -int aead_open( - const enum aeadType cipherId, +int openssl_aead_open( + const EVP_CIPHER* evpCipher, const unsigned char* const ciphertext, const int ciphertextLen, const unsigned char* const authData, diff --git a/internal/stupidgcm/stupidchacha.go b/internal/stupidgcm/stupidchacha.go deleted file mode 100644 index 5073aa3..0000000 --- a/internal/stupidgcm/stupidchacha.go +++ /dev/null @@ -1,156 +0,0 @@ -// +build !without_openssl - -package stupidgcm - -import ( - "crypto/cipher" - "fmt" - "log" - - "golang.org/x/crypto/chacha20poly1305" -) - -/* -#include <openssl/evp.h> -#include "chacha.h" -#cgo pkg-config: libcrypto -*/ -import "C" - -type stupidChacha20poly1305 struct { - key [chacha20poly1305.KeySize]byte - wiped bool -} - -// Verify that we satisfy the cipher.AEAD interface -var _ cipher.AEAD = &stupidChacha20poly1305{} - -func newChacha20poly1305(key []byte) cipher.AEAD { - if len(key) != chacha20poly1305.KeySize { - log.Panicf("Only %d-byte keys are supported, you passed %d bytes", chacha20poly1305.KeySize, len(key)) - } - ret := new(stupidChacha20poly1305) - copy(ret.key[:], key) - return ret -} - -// NonceSize returns the required size of the nonce / IV. -func (g *stupidChacha20poly1305) NonceSize() int { - return chacha20poly1305.NonceSize -} - -// Overhead returns the number of bytes that are added for authentication. -func (g *stupidChacha20poly1305) Overhead() int { - return tagLen -} - -// Seal encrypts "in" using "iv" and "authData" and append the result to "dst" -func (g *stupidChacha20poly1305) Seal(dst, iv, in, authData []byte) []byte { - if g.wiped { - panic("BUG: tried to use wiped key") - } - if len(iv) != g.NonceSize() { - log.Panicf("Only %d-byte IVs are supported, you passed %d bytes", g.NonceSize(), len(iv)) - } - if len(in) == 0 { - log.Panic("Zero-length input data is not supported") - } - if len(g.key) != chacha20poly1305.KeySize { - log.Panicf("Wrong key length: %d. Key has been wiped?", len(g.key)) - } - - // If the "dst" slice is large enough we can use it as our output buffer - outLen := len(in) + tagLen - var buf []byte - inplace := false - if cap(dst)-len(dst) >= outLen { - inplace = true - buf = dst[len(dst) : len(dst)+outLen] - } else { - buf = make([]byte, outLen) - } - - C.aead_seal(C.aeadTypeChacha, - (*C.uchar)(&in[0]), - C.int(len(in)), - (*C.uchar)(&authData[0]), - C.int(len(authData)), - (*C.uchar)(&g.key[0]), - C.int(len(g.key)), - (*C.uchar)(&iv[0]), - C.int(len(iv)), - (*C.uchar)(&buf[0]), - C.int(len(buf))) - - if inplace { - return dst[:len(dst)+outLen] - } - return append(dst, buf...) -} - -// Open decrypts "in" using "iv" and "authData" and append the result to "dst" -func (g *stupidChacha20poly1305) Open(dst, iv, in, authData []byte) ([]byte, error) { - if g.wiped { - panic("BUG: tried to use wiped key") - } - if len(iv) != g.NonceSize() { - log.Panicf("Only %d-byte IVs are supported", g.NonceSize()) - } - if len(g.key) != chacha20poly1305.KeySize { - log.Panicf("Wrong key length: %d. Key has been wiped?", len(g.key)) - } - if len(in) <= tagLen { - return nil, fmt.Errorf("stupidChacha20poly1305: input data too short (%d bytes)", len(in)) - } - - // If the "dst" slice is large enough we can use it as our output buffer - outLen := len(in) - tagLen - var buf []byte - inplace := false - if cap(dst)-len(dst) >= outLen { - inplace = true - buf = dst[len(dst) : len(dst)+outLen] - } else { - buf = make([]byte, len(in)-tagLen) - } - - ciphertext := in[:len(in)-tagLen] - tag := in[len(in)-tagLen:] - - res := int(C.aead_open(C.aeadTypeChacha, - (*C.uchar)(&ciphertext[0]), - C.int(len(ciphertext)), - (*C.uchar)(&authData[0]), - C.int(len(authData)), - (*C.uchar)(&tag[0]), - C.int(len(tag)), - (*C.uchar)(&g.key[0]), - C.int(len(g.key)), - (*C.uchar)(&iv[0]), - C.int(len(iv)), - (*C.uchar)(&buf[0]), - C.int(len(buf)))) - - if res < 0 { - return nil, ErrAuth - } - if res != outLen { - log.Panicf("unexpected length %d", res) - } - - if inplace { - return dst[:len(dst)+outLen], nil - } - return append(dst, buf...), nil -} - -// Wipe tries to wipe the key from memory by overwriting it with zeros. -// -// This is not bulletproof due to possible GC copies, but -// still raises the bar for extracting the key. -func (g *stupidChacha20poly1305) Wipe() { - g.wiped = true - for i := range g.key { - g.key[i] = 0 - } -} diff --git a/internal/stupidgcm/stupidgcm.go b/internal/stupidgcm/stupidgcm.go deleted file mode 100644 index 46b6b86..0000000 --- a/internal/stupidgcm/stupidgcm.go +++ /dev/null @@ -1,204 +0,0 @@ -// +build !without_openssl - -// Package stupidgcm is a thin wrapper for OpenSSL's GCM encryption and -// decryption functions. It only support 32-byte keys and 16-bit IVs. -package stupidgcm - -// #include <openssl/evp.h> -// #include "chacha.h" -// #cgo pkg-config: libcrypto -import "C" - -import ( - "crypto/cipher" - "fmt" - "log" - "unsafe" -) - -const ( - // BuiltWithoutOpenssl indicates if openssl been disabled at compile-time - BuiltWithoutOpenssl = false - - keyLen = 32 - ivLen = 16 - tagLen = 16 -) - -// StupidGCM implements the cipher.AEAD interface -type StupidGCM struct { - key []byte - forceDecode bool -} - -// Verify that we satisfy the cipher.AEAD interface -var _ cipher.AEAD = &StupidGCM{} - -// New returns a new cipher.AEAD implementation.. -func New(keyIn []byte, forceDecode bool) cipher.AEAD { - if len(keyIn) != keyLen { - log.Panicf("Only %d-byte keys are supported", keyLen) - } - // Create a private copy of the key - key := append([]byte{}, keyIn...) - return &StupidGCM{key: key, forceDecode: forceDecode} -} - -// NonceSize returns the required size of the nonce / IV. -func (g *StupidGCM) NonceSize() int { - return ivLen -} - -// Overhead returns the number of bytes that are added for authentication. -func (g *StupidGCM) Overhead() int { - return tagLen -} - -// Seal encrypts "in" using "iv" and "authData" and append the result to "dst" -func (g *StupidGCM) Seal(dst, iv, in, authData []byte) []byte { - if len(iv) != ivLen { - log.Panicf("Only %d-byte IVs are supported", ivLen) - } - if len(in) == 0 { - log.Panic("Zero-length input data is not supported") - } - if len(g.key) != keyLen { - log.Panicf("Wrong key length: %d. Key has been wiped?", len(g.key)) - } - - // If the "dst" slice is large enough we can use it as our output buffer - outLen := len(in) + tagLen - var buf []byte - inplace := false - if cap(dst)-len(dst) >= outLen { - inplace = true - buf = dst[len(dst) : len(dst)+outLen] - } else { - buf = make([]byte, outLen) - } - - C.aead_seal(C.aeadTypeGcm, - (*C.uchar)(&in[0]), - C.int(len(in)), - (*C.uchar)(&authData[0]), - C.int(len(authData)), - (*C.uchar)(&g.key[0]), - C.int(len(g.key)), - (*C.uchar)(&iv[0]), - C.int(len(iv)), - (*C.uchar)(&buf[0]), - C.int(len(buf))) - - if inplace { - return dst[:len(dst)+outLen] - } - return append(dst, buf...) -} - -// Open decrypts "in" using "iv" and "authData" and append the result to "dst" -func (g *StupidGCM) Open(dst, iv, in, authData []byte) ([]byte, error) { - if len(iv) != ivLen { - log.Panicf("Only %d-byte IVs are supported", ivLen) - } - if len(g.key) != keyLen { - log.Panicf("Wrong key length: %d. Key has been wiped?", len(g.key)) - } - if len(in) <= tagLen { - return nil, fmt.Errorf("stupidgcm: input data too short (%d bytes)", len(in)) - } - - // If the "dst" slice is large enough we can use it as our output buffer - outLen := len(in) - tagLen - var buf []byte - inplace := false - if cap(dst)-len(dst) >= outLen { - inplace = true - buf = dst[len(dst) : len(dst)+outLen] - } else { - buf = make([]byte, len(in)-tagLen) - } - - ciphertext := in[:len(in)-tagLen] - tag := in[len(in)-tagLen:] - - // https://wiki.openssl.org/index.php/EVP_Authenticated_Encryption_and_Decryption#Authenticated_Encryption_using_GCM_mode - - // Create scratch space "context" - ctx := C.EVP_CIPHER_CTX_new() - if ctx == nil { - log.Panic("EVP_CIPHER_CTX_new failed") - } - - // Set cipher to AES-256 - if C.EVP_DecryptInit_ex(ctx, C.EVP_aes_256_gcm(), nil, nil, nil) != 1 { - log.Panic("EVP_DecryptInit_ex I failed") - } - - // Use 16-byte IV - if C.EVP_CIPHER_CTX_ctrl(ctx, C.EVP_CTRL_GCM_SET_IVLEN, ivLen, nil) != 1 { - log.Panic("EVP_CIPHER_CTX_ctrl EVP_CTRL_GCM_SET_IVLEN failed") - } - - // Set key and IV - if C.EVP_DecryptInit_ex(ctx, nil, nil, (*C.uchar)(&g.key[0]), (*C.uchar)(&iv[0])) != 1 { - log.Panic("EVP_DecryptInit_ex II failed") - } - - // Set expected GMAC tag - if C.EVP_CIPHER_CTX_ctrl(ctx, C.EVP_CTRL_GCM_SET_TAG, tagLen, (unsafe.Pointer)(&tag[0])) != 1 { - log.Panic("EVP_CIPHER_CTX_ctrl failed") - } - - // Provide authentication data - var resultLen C.int - if C.EVP_DecryptUpdate(ctx, nil, &resultLen, (*C.uchar)(&authData[0]), C.int(len(authData))) != 1 { - log.Panic("EVP_DecryptUpdate authData failed") - } - if int(resultLen) != len(authData) { - log.Panicf("Unexpected length %d", resultLen) - } - - // Decrypt "ciphertext" into "buf" - if C.EVP_DecryptUpdate(ctx, (*C.uchar)(&buf[0]), &resultLen, (*C.uchar)(&ciphertext[0]), C.int(len(ciphertext))) != 1 { - log.Panic("EVP_DecryptUpdate failed") - } - if int(resultLen) != len(ciphertext) { - log.Panicf("Unexpected length %d", resultLen) - } - - // Check GMAC - dummy := make([]byte, 16) - res := C.EVP_DecryptFinal_ex(ctx, (*C.uchar)(&dummy[0]), &resultLen) - if resultLen != 0 { - log.Panicf("Unexpected length %d", resultLen) - } - - // Free scratch space - C.EVP_CIPHER_CTX_free(ctx) - - if res != 1 { - // The error code must always be checked by the calling function, because the decrypted buffer - // may contain corrupted data that we are returning in case the user forced reads - if g.forceDecode { - return append(dst, buf...), ErrAuth - } - return nil, ErrAuth - } - - if inplace { - return dst[:len(dst)+outLen], nil - } - return append(dst, buf...), nil -} - -// Wipe tries to wipe the AES key from memory by overwriting it with zeros -// and setting the reference to nil. -// -// This is not bulletproof due to possible GC copies, but -// still raises the bar for extracting the key. -func (g *StupidGCM) Wipe() { - for i := range g.key { - g.key[i] = 0 - } - g.key = nil -} diff --git a/internal/stupidgcm/stupidxchacha.go b/internal/stupidgcm/xchacha.go index 9f2ac2f..d8668dc 100644 --- a/internal/stupidgcm/stupidxchacha.go +++ b/internal/stupidgcm/xchacha.go @@ -17,6 +17,8 @@ import ( ) type stupidXchacha20poly1305 struct { + // array instead of byte slice like + // `struct xchacha20poly1305` in x/crypto/chacha20poly1305 key [chacha20poly1305.KeySize]byte wiped bool } @@ -41,7 +43,7 @@ func (*stupidXchacha20poly1305) NonceSize() int { } func (*stupidXchacha20poly1305) Overhead() int { - return 16 + return tagLen } func (x *stupidXchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []byte) []byte { @@ -61,9 +63,8 @@ func (x *stupidXchacha20poly1305) Seal(dst, nonce, plaintext, additionalData []b panic("plaintext too large") } - c := new(stupidChacha20poly1305) hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16]) - copy(c.key[:], hKey) + c := newChacha20poly1305(hKey) defer c.Wipe() // The first 4 bytes of the final nonce are unused counter space. @@ -87,9 +88,8 @@ func (x *stupidXchacha20poly1305) Open(dst, nonce, ciphertext, additionalData [] panic("ciphertext too large") } - c := new(stupidChacha20poly1305) hKey, _ := chacha20.HChaCha20(x.key[:], nonce[0:16]) - copy(c.key[:], hKey) + c := newChacha20poly1305(hKey) defer c.Wipe() // The first 4 bytes of the final nonce are unused counter space. diff --git a/internal/stupidgcm/stupidxchacha_test.go b/internal/stupidgcm/xchacha_test.go index fdea8b5..fdea8b5 100644 --- a/internal/stupidgcm/stupidxchacha_test.go +++ b/internal/stupidgcm/xchacha_test.go |