diff --git a/pkg/meta/internal/crypto/secretbox.go b/pkg/meta/internal/crypto/secretbox.go index 70d223c..690be7e 100644 --- a/pkg/meta/internal/crypto/secretbox.go +++ b/pkg/meta/internal/crypto/secretbox.go @@ -25,7 +25,8 @@ func GenerateKey() ([]byte, error) { return key, nil } -// EncryptWithKey encrypts data using secretbox with the provided key +// EncryptWithKey encrypts data using NaCl's secretbox with the provided key. +// 40 bytes of overhead (24-byte nonce + 16-byte MAC) are added to the plaintext size. func EncryptWithKey(data, key []byte) ([]byte, error) { if err := validateKey(key); err != nil { return nil, err diff --git a/pkg/meta/internal/crypto/secretbox_test.go b/pkg/meta/internal/crypto/secretbox_test.go index 09d3831..d87f860 100644 --- a/pkg/meta/internal/crypto/secretbox_test.go +++ b/pkg/meta/internal/crypto/secretbox_test.go @@ -2,12 +2,7 @@ package crypto import ( "bytes" - "crypto/aes" - "crypto/cipher" "crypto/rand" - "errors" - "fmt" - "io" "testing" "github.com/stretchr/testify/require" @@ -147,147 +142,3 @@ func tamperWithBytes(data []byte) []byte { tampered[24] ^= 0x01 // Modify first byte after nonce return tampered } - -func encryptWithAESKey(data, key []byte) ([]byte, error) { - if err := validateKey(key); err != nil { - return nil, err - } - - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - aesGCM, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonce := make([]byte, aesGCM.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return nil, err - } - - return aesGCM.Seal(nonce, nonce, data, nil), nil -} - -func decryptWithAESKey(data, key []byte) ([]byte, error) { - if err := validateKey(key); err != nil { - return nil, err - } - - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - aesGCM, err := cipher.NewGCM(block) - if err != nil { - return nil, err - } - - nonceSize := aesGCM.NonceSize() - if len(data) < nonceSize { - return nil, ErrShortCipherText - } - - nonce, ciphertext := data[:nonceSize], data[nonceSize:] - plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) - if err != nil { - return nil, errors.New("decryption failed") - } - - return plaintext, nil -} - -func BenchmarkEncryption(b *testing.B) { - key := make([]byte, keySize) - _, err := rand.Read(key) - require.NoError(b, err) - - sizes := []int{16, 64, 256, 1024, 4096} // Test different payload sizes - for _, size := range sizes { - data := make([]byte, size) - _, err := rand.Read(data) - require.NoError(b, err) - - b.Run(fmt.Sprintf("Secretbox-%dB", size), func(b *testing.B) { - for i := 0; i < b.N; i++ { - encrypted, err := EncryptWithKey(data, key) - require.NoError(b, err) - b.SetBytes(int64(len(encrypted))) - } - }) - - b.Run(fmt.Sprintf("AES-GCM-%dB", size), func(b *testing.B) { - for i := 0; i < b.N; i++ { - encrypted, err := encryptWithAESKey(data, key) - require.NoError(b, err) - b.SetBytes(int64(len(encrypted))) - } - }) - } -} - -func BenchmarkDecryption(b *testing.B) { - key := make([]byte, keySize) - _, err := rand.Read(key) - require.NoError(b, err) - - sizes := []int{16, 64, 256, 1024, 4096} - for _, size := range sizes { - data := make([]byte, size) - _, err := rand.Read(data) - require.NoError(b, err) - - secretboxEncrypted, err := EncryptWithKey(data, key) - require.NoError(b, err) - - aesGCMEncrypted, err := encryptWithAESKey(data, key) - require.NoError(b, err) - - b.Run(fmt.Sprintf("Secretbox-%dB", size), func(b *testing.B) { - for i := 0; i < b.N; i++ { - decrypted, err := DecryptStringWithKey(secretboxEncrypted, key) - require.NoError(b, err) - b.SetBytes(int64(len(decrypted))) - } - }) - - b.Run(fmt.Sprintf("AES-GCM-%dB", size), func(b *testing.B) { - for i := 0; i < b.N; i++ { - decrypted, err := decryptWithAESKey(aesGCMEncrypted, key) - require.NoError(b, err) - b.SetBytes(int64(len(decrypted))) - } - }) - } -} - -// TestCiphertextSizeComparison shows that Secretbox encryption entails -// a slightly larger ciphertext overhead of 40 bytes, compared to AES-GCM, -// whose overhead is just 28 bytes. -func TestCiphertextSizeComparison(t *testing.T) { - key := make([]byte, keySize) - _, err := rand.Read(key) - require.NoError(t, err) - - sizes := []int{0, 16, 64, 256, 1024, 4096} - for _, size := range sizes { - t.Run(fmt.Sprintf("size-%d", size), func(t *testing.T) { - data := make([]byte, size) - _, err := rand.Read(data) - require.NoError(t, err) - - sbCiphertext, err := EncryptWithKey(data, key) - require.NoError(t, err) - - aesCiphertext, err := encryptWithAESKey(data, key) - require.NoError(t, err) - - t.Logf("Input size: %d bytes", size) - t.Logf("Secretbox size: %d bytes (overhead: %d bytes)", len(sbCiphertext), len(sbCiphertext)-size) - t.Logf("AES-GCM size: %d bytes (overhead: %d bytes)", len(aesCiphertext), len(aesCiphertext)-size) - }) - } -} diff --git a/pkg/meta/meta.go b/pkg/meta/meta.go index 21d56a6..9b0e79f 100644 --- a/pkg/meta/meta.go +++ b/pkg/meta/meta.go @@ -150,6 +150,7 @@ func (m *Meta) Add(key string, val any) error { // AddEncrypted adds a key/value pair in the meta set. // The value is encrypted with the given encryptionKey. // Accepted types for the value are: string, []byte. +// The ciphertext will be 40 bytes larger than the plaintext due to encryption overhead. func (m *Meta) AddEncrypted(key string, val any, encryptionKey []byte) error { var encrypted []byte var err error diff --git a/token/delegation/options.go b/token/delegation/options.go index 4df14e7..3348760 100644 --- a/token/delegation/options.go +++ b/token/delegation/options.go @@ -45,7 +45,8 @@ func WithMeta(key string, val any) Option { } // WithEncryptedMetaString adds a key/value pair in the "meta" field. -// The string value is encrypted with the given aesKey. +// The string value is encrypted with the given key. +// The ciphertext will be 40 bytes larger than the plaintext due to encryption overhead. func WithEncryptedMetaString(key, val string, encryptionKey []byte) Option { return func(t *Token) error { return t.meta.AddEncrypted(key, val, encryptionKey) @@ -53,7 +54,8 @@ func WithEncryptedMetaString(key, val string, encryptionKey []byte) Option { } // WithEncryptedMetaBytes adds a key/value pair in the "meta" field. -// The []byte value is encrypted with the given aesKey. +// The []byte value is encrypted with the given key. +// The ciphertext will be 40 bytes larger than the plaintext due to encryption overhead. func WithEncryptedMetaBytes(key string, val, encryptionKey []byte) Option { return func(t *Token) error { return t.meta.AddEncrypted(key, val, encryptionKey)