summaryrefslogtreecommitdiff
path: root/crypto
diff options
context:
space:
mode:
authorDave Henderson <dhenderson@gmail.com>2022-02-12 16:57:23 -0500
committerGitHub <noreply@github.com>2022-02-12 16:57:23 -0500
commitd31b7c6d47dfdbcf0fbd3a323d41aa1935b558e4 (patch)
tree022c388f2ab70f004bcf318a01aacbe0b5969c08 /crypto
parent37a172433ae63e659675033b193b9eace7bf0666 (diff)
New crypto.EncryptAES/DecryptAES functions (#1305)
Signed-off-by: Dave Henderson <dhenderson@gmail.com>
Diffstat (limited to 'crypto')
-rw-r--r--crypto/aes.go88
-rw-r--r--crypto/aes_test.go67
2 files changed, 155 insertions, 0 deletions
diff --git a/crypto/aes.go b/crypto/aes.go
new file mode 100644
index 00000000..b3fab8dc
--- /dev/null
+++ b/crypto/aes.go
@@ -0,0 +1,88 @@
+package crypto
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "io"
+)
+
+// EncryptAESCBC - use a 128, 192, or 256 bit key to encrypt the given content
+// using AES-CBC. The output will not be encoded. Usually the output would be
+// base64-encoded for display. Empty content will not be encrypted.
+//
+// This function is compatible with Helm's decryptAES function, when the output
+// is base64-encoded, and when the key is 256 bits long.
+func EncryptAESCBC(key []byte, in []byte) ([]byte, error) {
+ if len(in) == 0 {
+ return in, nil
+ }
+
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+
+ // Pad the content to be a multiple of the block size, with the pad length
+ // as the content. Note that the padding will be a full block when the
+ // content is already a multiple of the block size.
+ // This algorithm is described in the TLS spec:
+ // https://datatracker.ietf.org/doc/html/rfc5246#section-6.2.3.2
+ bs := block.BlockSize()
+ pl := bs - len(in)%bs
+
+ // pad with pl, repeated pl times
+ in = append(in, bytes.Repeat([]byte{byte(pl)}, pl)...)
+
+ out := make([]byte, bs+len(in))
+
+ // Generate a random IV. Must be the same length as the block size, and is
+ // stored at the beginning of the output slice unencrypted, so that it can
+ // be used for decryption.
+ iv := out[:bs]
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return nil, err
+ }
+
+ // encrypt the content into the rest of the output slice
+ mode := cipher.NewCBCEncrypter(block, iv)
+ mode.CryptBlocks(out[bs:], in)
+
+ return out, nil
+}
+
+// DecryptAESCBC - use a 128, 192, or 256 bit key to decrypt the given content
+// using AES-CBC. The output will not be encoded. Empty content will not be
+// decrypted.
+//
+// This function is compatible with Helm's encryptAES function, when the input
+// is base64-decoded, and when the key is 256 bits long.
+func DecryptAESCBC(key []byte, in []byte) ([]byte, error) {
+ if len(in) == 0 {
+ return nil, nil
+ }
+
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return nil, err
+ }
+
+ // the first block is the IV, unencrypted
+ iv := in[:aes.BlockSize]
+
+ // the rest of the content is encrypted
+ in = in[aes.BlockSize:]
+
+ out := make([]byte, len(in))
+
+ mode := cipher.NewCBCDecrypter(block, iv)
+ mode.CryptBlocks(out, in)
+
+ // content must always be padded with at least one byte, and the padding
+ // byte must be the padding length
+ pl := int(out[len(out)-1])
+ out = out[:len(out)-pl]
+
+ return out, nil
+}
diff --git a/crypto/aes_test.go b/crypto/aes_test.go
new file mode 100644
index 00000000..df00b14b
--- /dev/null
+++ b/crypto/aes_test.go
@@ -0,0 +1,67 @@
+package crypto
+
+import (
+ "bytes"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestEncryptDecryptAESCBC(t *testing.T) {
+ // empty key is invalid
+ _, err := EncryptAESCBC([]byte{}, []byte("foo"))
+ assert.Error(t, err)
+
+ // wrong-length keys are invalid
+ _, err = EncryptAESCBC(bytes.Repeat([]byte{'a'}, 1), []byte("foo"))
+ assert.Error(t, err)
+
+ _, err = EncryptAESCBC(bytes.Repeat([]byte{'a'}, 15), []byte("foo"))
+ assert.Error(t, err)
+
+ key := make([]byte, 32)
+ copy(key, []byte("password"))
+
+ // empty content is a pass-through
+ out, err := EncryptAESCBC(key, []byte{})
+ assert.NoError(t, err)
+ assert.Equal(t, []byte{}, out)
+
+ testdata := [][]byte{
+ bytes.Repeat([]byte{'a'}, 1),
+ bytes.Repeat([]byte{'a'}, 15),
+ bytes.Repeat([]byte{'a'}, 16),
+ bytes.Repeat([]byte{'a'}, 31),
+ bytes.Repeat([]byte{'a'}, 32),
+ }
+
+ for _, d := range testdata {
+ out, err = EncryptAESCBC(key, d)
+ assert.NoError(t, err)
+ assert.NotEqual(t, d, out)
+
+ out, err = DecryptAESCBC(key, out)
+ assert.NoError(t, err)
+ assert.Equal(t, d, out)
+ }
+
+ // 128-bit key
+ key = bytes.Repeat([]byte{'a'}, 16)
+ out, err = EncryptAESCBC(key, []byte("foo"))
+ assert.NoError(t, err)
+ assert.NotEqual(t, []byte("foo"), out)
+
+ out, err = DecryptAESCBC(key, out)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("foo"), out)
+
+ // 192-bit key
+ key = bytes.Repeat([]byte{'a'}, 24)
+ out, err = EncryptAESCBC(key, []byte("foo"))
+ assert.NoError(t, err)
+ assert.NotEqual(t, []byte("foo"), out)
+
+ out, err = DecryptAESCBC(key, out)
+ assert.NoError(t, err)
+ assert.Equal(t, []byte("foo"), out)
+}