Repository: Azure/ARO-RP
Pull request #1226 (2020-12-14 17:24:45)

Add AEAD_AES_256_CBC_HMAC_SHA_512 encryption mode library
### Which issue this PR addresses:

Related to https://msazure.visualstudio.com/AzureRedHatOpenShift/_workitems/edit/8971702

### What this PR does / why we need it:

Adds AEAD_AES_256_CBC_HMAC_SHA_512 encryption mode library in preparation to migrating away from XChaCha.

### Test plan for issue:

Unit tests + E2E.

### Is there any documentation that needs to be updated for this PR?

No.


Patch:
diff --git a/cmd/aro/monitor.go b/cmd/aro/monitor.go
index bba6d97b5..164ccf531 100644
--- a/cmd/aro/monitor.go
+++ b/cmd/aro/monitor.go
@@ -79,12 +79,12 @@ func monitor(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	cipher, err := encryption.NewXChaCha20Poly1305(ctx, key)
+	aead, err := encryption.NewXChaCha20Poly1305(ctx, key)
 	if err != nil {
 		return err
 	}
 
-	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, &noop.Noop{}, cipher)
+	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, &noop.Noop{}, aead)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/aro/portal.go b/cmd/aro/portal.go
index 6c50c1e06..a36aecfa1 100644
--- a/cmd/aro/portal.go
+++ b/cmd/aro/portal.go
@@ -87,12 +87,12 @@ func portal(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	cipher, err := encryption.NewXChaCha20Poly1305(ctx, key)
+	aead, err := encryption.NewXChaCha20Poly1305(ctx, key)
 	if err != nil {
 		return err
 	}
 
-	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, m, cipher)
+	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, m, aead)
 	if err != nil {
 		return err
 	}
diff --git a/cmd/aro/rp.go b/cmd/aro/rp.go
index 207294860..427caee62 100644
--- a/cmd/aro/rp.go
+++ b/cmd/aro/rp.go
@@ -81,12 +81,12 @@ func rp(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	cipher, err := encryption.NewXChaCha20Poly1305(ctx, dbKey)
+	aead, err := encryption.NewXChaCha20Poly1305(ctx, dbKey)
 	if err != nil {
 		return err
 	}
 
-	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, m, cipher)
+	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, m, aead)
 	if err != nil {
 		return err
 	}
@@ -118,17 +118,17 @@ func rp(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	feCipher, err := encryption.NewXChaCha20Poly1305(ctx, feKey)
+	feAead, err := encryption.NewXChaCha20Poly1305(ctx, feKey)
 	if err != nil {
 		return err
 	}
 
-	f, err := frontend.NewFrontend(ctx, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbOpenShiftClusters, dbSubscriptions, api.APIs, m, feCipher, adminactions.New)
+	f, err := frontend.NewFrontend(ctx, log.WithField("component", "frontend"), _env, dbAsyncOperations, dbOpenShiftClusters, dbSubscriptions, api.APIs, m, feAead, adminactions.New)
 	if err != nil {
 		return err
 	}
 
-	b, err := backend.NewBackend(ctx, log.WithField("component", "backend"), _env, dbAsyncOperations, dbBilling, dbOpenShiftClusters, dbSubscriptions, cipher, m)
+	b, err := backend.NewBackend(ctx, log.WithField("component", "backend"), _env, dbAsyncOperations, dbBilling, dbOpenShiftClusters, dbSubscriptions, aead, m)
 	if err != nil {
 		return err
 	}
diff --git a/go.mod b/go.mod
index 4321ad202..3c1308522 100644
--- a/go.mod
+++ b/go.mod
@@ -23,6 +23,7 @@ require (
 	github.com/apparentlymart/go-cidr v1.1.0
 	github.com/aws/aws-sdk-go v1.36.0 // indirect
 	github.com/axw/gocov v1.0.0
+	github.com/codahale/etm v0.0.0-20141003032925-c00c9e6fb4c9
 	github.com/containers/image/v5 v5.8.1
 	github.com/containers/libtrust v0.0.0-20200511145503-9c3a6c22cd9a // indirect
 	github.com/containers/storage v1.24.1 // indirect
diff --git a/go.sum b/go.sum
index 18d120855..a0d11fd69 100644
--- a/go.sum
+++ b/go.sum
@@ -341,6 +341,8 @@ github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnht
 github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
 github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk=
 github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
+github.com/codahale/etm v0.0.0-20141003032925-c00c9e6fb4c9 h1:88tJLy+/ao5kPBv1EtNyduXeWrTHV47seJPgI7pWgDs=
+github.com/codahale/etm v0.0.0-20141003032925-c00c9e6fb4c9/go.mod h1:jy75q4Q7stkoOx8bCRnIm0t1Vh6Pt4OJvcwA9+oJsqI=
 github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
 github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
 github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko=
diff --git a/hack/db/db.go b/hack/db/db.go
index dd96d7b22..fae112590 100644
--- a/hack/db/db.go
+++ b/hack/db/db.go
@@ -48,12 +48,12 @@ func run(ctx context.Context, log *logrus.Entry) error {
 		return err
 	}
 
-	cipher, err := encryption.NewXChaCha20Poly1305(ctx, key)
+	aead, err := encryption.NewXChaCha20Poly1305(ctx, key)
 	if err != nil {
 		return err
 	}
 
-	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, &noop.Noop{}, cipher)
+	dbc, err := database.NewDatabaseClient(ctx, log.WithField("component", "database"), _env, &noop.Noop{}, aead)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/backend/backend.go b/pkg/backend/backend.go
index 1f1186f04..d96dd550a 100644
--- a/pkg/backend/backend.go
+++ b/pkg/backend/backend.go
@@ -33,7 +33,7 @@ type backend struct {
 	dbOpenShiftClusters database.OpenShiftClusters
 	dbSubscriptions     database.Subscriptions
 
-	cipher  encryption.Cipher
+	aead    encryption.AEAD
 	m       metrics.Interface
 	billing billing.Manager
 
@@ -52,8 +52,8 @@ type Runnable interface {
 }
 
 // NewBackend returns a new runnable backend
-func NewBackend(ctx context.Context, log *logrus.Entry, env env.Interface, dbAsyncOperations database.AsyncOperations, dbBilling database.Billing, dbOpenShiftClusters database.OpenShiftClusters, dbSubscriptions database.Subscriptions, cipher encryption.Cipher, m metrics.Interface) (Runnable, error) {
-	b, err := newBackend(ctx, log, env, dbAsyncOperations, dbBilling, dbOpenShiftClusters, dbSubscriptions, cipher, m)
+func NewBackend(ctx context.Context, log *logrus.Entry, env env.Interface, dbAsyncOperations database.AsyncOperations, dbBilling database.Billing, dbOpenShiftClusters database.OpenShiftClusters, dbSubscriptions database.Subscriptions, aead encryption.AEAD, m metrics.Interface) (Runnable, error) {
+	b, err := newBackend(ctx, log, env, dbAsyncOperations, dbBilling, dbOpenShiftClusters, dbSubscriptions, aead, m)
 	if err != nil {
 		return nil, err
 	}
@@ -63,7 +63,7 @@ func NewBackend(ctx context.Context, log *logrus.Entry, env env.Interface, dbAsy
 	return b, nil
 }
 
-func newBackend(ctx context.Context, log *logrus.Entry, env env.Interface, dbAsyncOperations database.AsyncOperations, dbBilling database.Billing, dbOpenShiftClusters database.OpenShiftClusters, dbSubscriptions database.Subscriptions, cipher encryption.Cipher, m metrics.Interface) (*backend, error) {
+func newBackend(ctx context.Context, log *logrus.Entry, env env.Interface, dbAsyncOperations database.AsyncOperations, dbBilling database.Billing, dbOpenShiftClusters database.OpenShiftClusters, dbSubscriptions database.Subscriptions, aead encryption.AEAD, m metrics.Interface) (*backend, error) {
 	billing, err := billing.NewManager(env, dbBilling, dbSubscriptions, log)
 	if err != nil {
 		return nil, err
@@ -79,7 +79,7 @@ func newBackend(ctx context.Context, log *logrus.Entry, env env.Interface, dbAsy
 		dbSubscriptions:     dbSubscriptions,
 
 		billing: billing,
-		cipher:  cipher,
+		aead:    aead,
 		m:       m,
 	}
 	b.cond = sync.NewCond(&b.mu)
diff --git a/pkg/backend/openshiftcluster.go b/pkg/backend/openshiftcluster.go
index 579c50241..c6cc41124 100644
--- a/pkg/backend/openshiftcluster.go
+++ b/pkg/backend/openshiftcluster.go
@@ -27,7 +27,7 @@ import (
 type openShiftClusterBackend struct {
 	*backend
 
-	newManager func(context.Context, *logrus.Entry, env.Interface, database.OpenShiftClusters, encryption.Cipher, billing.Manager, *api.OpenShiftClusterDocument, *api.SubscriptionDocument) (cluster.Interface, error)
+	newManager func(context.Context, *logrus.Entry, env.Interface, database.OpenShiftClusters, encryption.AEAD, billing.Manager, *api.OpenShiftClusterDocument, *api.SubscriptionDocument) (cluster.Interface, error)
 }
 
 func newOpenShiftClusterBackend(b *backend) *openShiftClusterBackend {
@@ -100,7 +100,7 @@ func (ocb *openShiftClusterBackend) handle(ctx context.Context, log *logrus.Entr
 		return err
 	}
 
-	m, err := ocb.newManager(ctx, log, ocb.env, ocb.dbOpenShiftClusters, ocb.cipher, ocb.billing, doc, subscriptionDoc)
+	m, err := ocb.newManager(ctx, log, ocb.env, ocb.dbOpenShiftClusters, ocb.aead, ocb.billing, doc, subscriptionDoc)
 	if err != nil {
 		return ocb.endLease(ctx, log, stop, doc, api.ProvisioningStateFailed, err)
 	}
diff --git a/pkg/cluster/cluster.go b/pkg/cluster/cluster.go
index 90f54dae6..20e0c0203 100644
--- a/pkg/cluster/cluster.go
+++ b/pkg/cluster/cluster.go
@@ -47,7 +47,7 @@ type manager struct {
 	billing           billing.Manager
 	doc               *api.OpenShiftClusterDocument
 	subscriptionDoc   *api.SubscriptionDocument
-	cipher            encryption.Cipher
+	aead              encryption.AEAD
 	fpAuthorizer      refreshable.Authorizer
 	localFpAuthorizer refreshable.Authorizer
 
@@ -79,7 +79,7 @@ type manager struct {
 const deploymentName = "azuredeploy"
 
 // New returns a cluster manager
-func New(ctx context.Context, log *logrus.Entry, env env.Interface, db database.OpenShiftClusters, cipher encryption.Cipher,
+func New(ctx context.Context, log *logrus.Entry, env env.Interface, db database.OpenShiftClusters, aead encryption.AEAD,
 	billing billing.Manager, doc *api.OpenShiftClusterDocument, subscriptionDoc *api.SubscriptionDocument) (Interface, error) {
 	r, err := azure.ParseResourceID(doc.OpenShiftCluster.ID)
 	if err != nil {
@@ -103,7 +103,7 @@ func New(ctx context.Context, log *logrus.Entry, env env.Interface, db database.
 		billing:           billing,
 		doc:               doc,
 		subscriptionDoc:   subscriptionDoc,
-		cipher:            cipher,
+		aead:              aead,
 		fpAuthorizer:      fpAuthorizer,
 		localFpAuthorizer: localFPAuthorizer,
 
diff --git a/pkg/cluster/graph.go b/pkg/cluster/graph.go
index ed8c1d2b4..b15a50dbe 100644
--- a/pkg/cluster/graph.go
+++ b/pkg/cluster/graph.go
@@ -212,18 +212,18 @@ func (m *manager) loadGraph(ctx context.Context) (graph, error) {
 	}
 	defer rc.Close()
 
-	encrypted, err := ioutil.ReadAll(rc)
+	b, err := ioutil.ReadAll(rc)
 	if err != nil {
 		return nil, err
 	}
 
-	output, err := m.cipher.Decrypt(encrypted)
+	b, err = m.aead.Open(b)
 	if err != nil {
 		return nil, err
 	}
 
 	var g graph
-	err = json.Unmarshal(output, &g)
+	err = json.Unmarshal(b, &g)
 	if err != nil {
 		return nil, err
 	}
@@ -252,10 +252,10 @@ func (m *manager) saveGraph(ctx context.Context, g graph) error {
 		return err
 	}
 
-	output, err := m.cipher.Encrypt(b)
+	b, err = m.aead.Seal(b)
 	if err != nil {
 		return err
 	}
 
-	return graph.CreateBlockBlobFromReader(bytes.NewReader(output), nil)
+	return graph.CreateBlockBlobFromReader(bytes.NewReader(b), nil)
 }
diff --git a/pkg/database/database.go b/pkg/database/database.go
index ecde256df..f02f4edc9 100644
--- a/pkg/database/database.go
+++ b/pkg/database/database.go
@@ -34,13 +34,13 @@ const (
 	collSubscriptions     = "Subscriptions"
 )
 
-func NewDatabaseClient(ctx context.Context, log *logrus.Entry, env env.Core, m metrics.Interface, cipher encryption.Cipher) (cosmosdb.DatabaseClient, error) {
+func NewDatabaseClient(ctx context.Context, log *logrus.Entry, env env.Core, m metrics.Interface, aead encryption.AEAD) (cosmosdb.DatabaseClient, error) {
 	databaseAccount, masterKey, err := find(ctx, env)
 	if err != nil {
 		return nil, err
 	}
 
-	h, err := NewJSONHandle(cipher)
+	h, err := NewJSONHandle(aead)
 	if err != nil {
 		return nil, err
 	}
@@ -58,7 +58,7 @@ func NewDatabaseClient(ctx context.Context, log *logrus.Entry, env env.Core, m m
 	return cosmosdb.NewDatabaseClient(log, c, h, databaseHostname, masterKey)
 }
 
-func NewJSONHandle(cipher encryption.Cipher) (*codec.JsonHandle, error) {
+func NewJSONHandle(aead encryption.AEAD) (*codec.JsonHandle, error) {
 	h := &codec.JsonHandle{
 		BasicHandle: codec.BasicHandle{
 			DecodeOptions: codec.DecodeOptions{
@@ -67,12 +67,12 @@ func NewJSONHandle(cipher encryption.Cipher) (*codec.JsonHandle, error) {
 		},
 	}
 
-	err := h.SetInterfaceExt(reflect.TypeOf(api.SecureBytes{}), 1, secureBytesExt{cipher: cipher})
+	err := h.SetInterfaceExt(reflect.TypeOf(api.SecureBytes{}), 1, secureBytesExt{aead: aead})
 	if err != nil {
 		return nil, err
 	}
 
-	err = h.SetInterfaceExt(reflect.TypeOf((*api.SecureString)(nil)), 1, secureStringExt{cipher: cipher})
+	err = h.SetInterfaceExt(reflect.TypeOf((*api.SecureString)(nil)), 1, secureStringExt{aead: aead})
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/database/extensions.go b/pkg/database/extensions.go
index 109d25331..8cbcd1c0f 100644
--- a/pkg/database/extensions.go
+++ b/pkg/database/extensions.go
@@ -15,16 +15,16 @@ import (
 var _ codec.InterfaceExt = (*secureBytesExt)(nil)
 
 type secureBytesExt struct {
-	cipher encryption.Cipher
+	aead encryption.AEAD
 }
 
 func (s secureBytesExt) ConvertExt(v interface{}) interface{} {
-	encrypted, err := s.cipher.Encrypt(v.(api.SecureBytes))
+	b, err := s.aead.Seal(v.(api.SecureBytes))
 	if err != nil {
 		panic(err)
 	}
 
-	return base64.StdEncoding.EncodeToString([]byte(encrypted))
+	return base64.StdEncoding.EncodeToString(b)
 }
 
 func (s secureBytesExt) UpdateExt(dest interface{}, v interface{}) {
@@ -33,7 +33,7 @@ func (s secureBytesExt) UpdateExt(dest interface{}, v interface{}) {
 		panic(err)
 	}
 
-	b, err = s.cipher.Decrypt(b)
+	b, err = s.aead.Open(b)
 	if err != nil {
 		panic(err)
 	}
@@ -44,16 +44,16 @@ func (s secureBytesExt) UpdateExt(dest interface{}, v interface{}) {
 var _ codec.InterfaceExt = (*secureStringExt)(nil)
 
 type secureStringExt struct {
-	cipher encryption.Cipher
+	aead encryption.AEAD
 }
 
 func (s secureStringExt) ConvertExt(v interface{}) interface{} {
-	encrypted, err := s.cipher.Encrypt([]byte(v.(api.SecureString)))
+	b, err := s.aead.Seal([]byte(v.(api.SecureString)))
 	if err != nil {
 		panic(err)
 	}
 
-	return base64.StdEncoding.EncodeToString([]byte(encrypted))
+	return base64.StdEncoding.EncodeToString(b)
 }
 
 func (s secureStringExt) UpdateExt(dest interface{}, v interface{}) {
@@ -62,7 +62,7 @@ func (s secureStringExt) UpdateExt(dest interface{}, v interface{}) {
 		panic(err)
 	}
 
-	b, err = s.cipher.Decrypt(b)
+	b, err = s.aead.Open(b)
 	if err != nil {
 		panic(err)
 	}
diff --git a/pkg/frontend/frontend.go b/pkg/frontend/frontend.go
index 2f83ed1d7..0d680c224 100644
--- a/pkg/frontend/frontend.go
+++ b/pkg/frontend/frontend.go
@@ -48,9 +48,9 @@ type frontend struct {
 	dbOpenShiftClusters database.OpenShiftClusters
 	dbSubscriptions     database.Subscriptions
 
-	apis   map[string]*api.Version
-	m      metrics.Interface
-	cipher encryption.Cipher
+	apis map[string]*api.Version
+	m    metrics.Interface
+	aead encryption.AEAD
 
 	ocEnricher          clusterdata.OpenShiftClusterEnricher
 	adminActionsFactory adminActionsFactory
@@ -78,7 +78,7 @@ func NewFrontend(ctx context.Context,
 	dbSubscriptions database.Subscriptions,
 	apis map[string]*api.Version,
 	m metrics.Interface,
-	cipher encryption.Cipher,
+	aead encryption.AEAD,
 	adminActionsFactory adminActionsFactory) (Runnable, error) {
 	f := &frontend{
 		baseLog:             baseLog,
@@ -88,7 +88,7 @@ func NewFrontend(ctx context.Context,
 		dbSubscriptions:     dbSubscriptions,
 		apis:                apis,
 		m:                   m,
-		cipher:              cipher,
+		aead:                aead,
 		adminActionsFactory: adminActionsFactory,
 
 		ocEnricher: clusterdata.NewBestEffortEnricher(baseLog, _env, m),
diff --git a/pkg/frontend/openshiftcluster_list.go b/pkg/frontend/openshiftcluster_list.go
index 986fe80f8..45bb30eff 100644
--- a/pkg/frontend/openshiftcluster_list.go
+++ b/pkg/frontend/openshiftcluster_list.go
@@ -94,7 +94,7 @@ func (f *frontend) parseSkipToken(originalURL string) (string, error) {
 		return "", err
 	}
 
-	output, err := f.cipher.Decrypt(b)
+	output, err := f.aead.Open(b)
 	if err != nil {
 		return "", err
 	}
@@ -114,7 +114,7 @@ func (f *frontend) buildNextLink(baseURL, skipToken string) (string, error) {
 		return "", err
 	}
 
-	output, err := f.cipher.Encrypt([]byte(skipToken))
+	output, err := f.aead.Seal([]byte(skipToken))
 	if err != nil {
 		return "", err
 	}
diff --git a/pkg/util/encryption/aead.go b/pkg/util/encryption/aead.go
new file mode 100644
index 000000000..2d88b881c
--- /dev/null
+++ b/pkg/util/encryption/aead.go
@@ -0,0 +1,9 @@
+package encryption
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+type AEAD interface {
+	Open([]byte) ([]byte, error)
+	Seal([]byte) ([]byte, error)
+}
diff --git a/pkg/util/encryption/aescbc.go b/pkg/util/encryption/aescbc.go
new file mode 100644
index 000000000..b6712b8a5
--- /dev/null
+++ b/pkg/util/encryption/aescbc.go
@@ -0,0 +1,52 @@
+package encryption
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"crypto/cipher"
+	"crypto/rand"
+	"fmt"
+	"io"
+
+	"github.com/codahale/etm"
+)
+
+type aes256Sha512 struct {
+	aead       cipher.AEAD
+	randReader io.Reader
+}
+
+var _ AEAD = (*aes256Sha512)(nil)
+
+func NewAES256SHA512(ctx context.Context, key []byte) (AEAD, error) {
+	aead, err := etm.NewAES256SHA512(key)
+	if err != nil {
+		return nil, err
+	}
+
+	return &aes256Sha512{
+		aead:       aead,
+		randReader: rand.Reader,
+	}, nil
+}
+
+func (c *aes256Sha512) Open(input []byte) ([]byte, error) {
+	if len(input) < 32 {
+		return nil, fmt.Errorf("encrypted value too short")
+	}
+
+	return c.aead.Open(nil, nil, input, nil)
+}
+
+func (c *aes256Sha512) Seal(input []byte) ([]byte, error) {
+	nonce := make([]byte, c.aead.NonceSize())
+
+	_, err := io.ReadFull(c.randReader, nonce)
+	if err != nil {
+		return nil, err
+	}
+
+	return c.aead.Seal(nil, nonce, input, nil), nil
+}
diff --git a/pkg/util/encryption/xchacha20poly1305.go b/pkg/util/encryption/xchacha20poly1305.go
index 75a134081..2daace139 100644
--- a/pkg/util/encryption/xchacha20poly1305.go
+++ b/pkg/util/encryption/xchacha20poly1305.go
@@ -8,56 +8,48 @@ import (
 	"crypto/cipher"
 	"crypto/rand"
 	"fmt"
+	"io"
 
 	"golang.org/x/crypto/chacha20poly1305"
 )
 
-var _ Cipher = (*aeadCipher)(nil)
-
-type Cipher interface {
-	Decrypt([]byte) ([]byte, error)
-	Encrypt([]byte) ([]byte, error)
+type xChaCha20Poly1305 struct {
+	aead       cipher.AEAD
+	randReader io.Reader
 }
 
-type aeadCipher struct {
-	aead     cipher.AEAD
-	randRead func([]byte) (int, error)
-}
+var _ AEAD = (*xChaCha20Poly1305)(nil)
 
-func NewXChaCha20Poly1305(ctx context.Context, key []byte) (Cipher, error) {
+func NewXChaCha20Poly1305(ctx context.Context, key []byte) (AEAD, error) {
 	aead, err := chacha20poly1305.NewX(key)
 	if err != nil {
 		return nil, err
 	}
 
-	return &aeadCipher{
-		aead:     aead,
-		randRead: rand.Read,
+	return &xChaCha20Poly1305{
+		aead:       aead,
+		randReader: rand.Reader,
 	}, nil
 }
 
-func (c *aeadCipher) Decrypt(input []byte) ([]byte, error) {
-	if len(input) < chacha20poly1305.NonceSizeX {
+func (c *xChaCha20Poly1305) Open(input []byte) ([]byte, error) {
+	if len(input) < c.aead.NonceSize() {
 		return nil, fmt.Errorf("encrypted value too short")
 	}
 
-	nonce := input[:chacha20poly1305.NonceSizeX]
-	data := input[chacha20poly1305.NonceSizeX:]
+	nonce := input[:c.aead.NonceSize()]
+	data := input[c.aead.NonceSize():]
 
 	return c.aead.Open(nil, nonce, data, nil)
 }
 
-func (c *aeadCipher) Encrypt(input []byte) ([]byte, error) {
-	nonce := make([]byte, chacha20poly1305.NonceSizeX)
+func (c *xChaCha20Poly1305) Seal(input []byte) ([]byte, error) {
+	nonce := make([]byte, c.aead.NonceSize())
 
-	n, err := c.randRead(nonce)
+	_, err := io.ReadFull(c.randReader, nonce)
 	if err != nil {
 		return nil, err
 	}
 
-	if n != chacha20poly1305.NonceSizeX {
-		return nil, fmt.Errorf("rand.Read returned %d bytes, expected %d", n, chacha20poly1305.NonceSizeX)
-	}
-
 	return append(nonce, c.aead.Seal(nil, nonce, input, nil)...), nil
 }
diff --git a/vendor/github.com/codahale/etm/.travis.yml b/vendor/github.com/codahale/etm/.travis.yml
new file mode 100644
index 000000000..46cc6e78c
--- /dev/null
+++ b/vendor/github.com/codahale/etm/.travis.yml
@@ -0,0 +1,9 @@
+language: go
+go:
+  - 1.3.3
+notifications:
+  # See http://about.travis-ci.org/docs/user/build-configuration/ to learn more
+  # about configuring notification recipients and more.
+  email:
+    recipients:
+      - coda.hale@gmail.com
diff --git a/vendor/github.com/codahale/etm/LICENSE b/vendor/github.com/codahale/etm/LICENSE
new file mode 100644
index 000000000..f9835c241
--- /dev/null
+++ b/vendor/github.com/codahale/etm/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Coda Hale
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/github.com/codahale/etm/README.md b/vendor/github.com/codahale/etm/README.md
new file mode 100644
index 000000000..c9f2dfc25
--- /dev/null
+++ b/vendor/github.com/codahale/etm/README.md
@@ -0,0 +1,9 @@
+etm
+===
+
+[![Build Status](https://travis-ci.org/codahale/etm.png?branch=master)](https://travis-ci.org/codahale/etm)
+
+A Go implementation of AES/CBC/HMAC-based AEAD constructions from
+[draft-mcgrew-aead-aes-cbc-hmac-sha2-02](http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-02).
+
+For documentation, check [godoc](http://godoc.org/github.com/codahale/etm).
diff --git a/vendor/github.com/codahale/etm/etm.go b/vendor/github.com/codahale/etm/etm.go
new file mode 100644
index 000000000..fc12e7306
--- /dev/null
+++ b/vendor/github.com/codahale/etm/etm.go
@@ -0,0 +1,270 @@
+// Package etm provides a set of Encrypt-Then-MAC AEAD implementations, which
+// combine block ciphers like AES with HMACs.
+//
+// AEADs
+//
+// An AEAD (Authenticated Encryption with Associated Data) construction provides
+// a unified API for sealing messages in a way which provides both
+// confidentiality *and* integrity.
+//
+// This not only prevents malicious tampering but also eliminates online attacks
+// like padding oracle attacks which can allow an attacker to recover plaintexts
+// without knowledge of the secret key (e.g., Lucky 13 attack, BEAST attack,
+// etc.).
+//
+// By rejecting ciphertexts which have been modified, these types of attacks are
+// eliminated.
+//
+// Constructions
+//
+// This package implements five proposed standards:
+//
+//		AEAD_AES_128_CBC_HMAC_SHA_256
+//		AEAD_AES_192_CBC_HMAC_SHA_384
+//		AEAD_AES_256_CBC_HMAC_SHA_384
+//		AEAD_AES_256_CBC_HMAC_SHA_512
+//		AEAD_AES_128_CBC_HMAC_SHA1
+//
+// All five constructions combine AES in CBC mode with an HMAC, but vary in the
+// degree of security offered and the amount of overhead required. See
+// http://tools.ietf.org/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-02 for full
+// technical details.
+//
+// AES-128-CBC-HMAC-SHA-256
+//
+// AEAD_AES_128_CBC_HMAC_SHA_256 requires a 32-byte key, provides 128 bits of
+// security for both confidentiality and integrity, and adds up to 56 bytes of
+// overhead per message.
+//
+// AES-192-CBC-HMAC-SHA-384
+//
+// AEAD_AES_192_CBC_HMAC_SHA_384 requires a 48-byte key, provides 192 bits of
+// security for both confidentiality and integrity, and adds up to 64 bytes of
+// overhead per message.
+//
+// AES-256-CBC-HMAC-SHA-384
+//
+// AEAD_AES_256_CBC_HMAC_SHA_384 requires a 56-byte key, provides 256 bits of
+// security for confidentiality, provides 192 bits of security for integrity, and
+// adds up to 64 bytes of overhead per message.
+//
+// AES-256-CBC-HMAC-SHA-512
+//
+// AEAD_AES_256_CBC_HMAC_SHA_512 requires a 64-byte key, provides 256 bits of
+// security for both confidentiality and integrity, and adds up to 72 bytes of
+// overhead per message.
+//
+// AES-128-CBC-HMAC-SHA1
+//
+// AEAD_AES_128_CBC_HMAC_SHA1 requires a 36-byte key, provides 128 bits of
+// security for confidentiality, provides 96 bits of security for integrity, and
+// adds up to 52 bytes of overhead per message. (This construction uses SHA-1,
+// and should only be used when the use of SHA2 is not possible.)
+package etm
+
+import (
+	"crypto/aes"
+	"crypto/cipher"
+	"crypto/hmac"
+	"crypto/sha1"
+	"crypto/sha256"
+	"crypto/sha512"
+	"encoding/binary"
+	"errors"
+	"fmt"
+	"hash"
+)
+
+// NewAES128SHA256 returns an AEAD_AES_128_CBC_HMAC_SHA_256 instance given a
+// 32-byte key or an error if the key is the wrong size.
+// AEAD_AES_128_CBC_HMAC_SHA_256 combines AES-128 in CBC mode with
+// HMAC-SHA-256-128.
+func NewAES128SHA256(key []byte) (cipher.AEAD, error) {
+	return create(etmParams{
+		cipherParams: aesCBC,
+		macAlg:       sha256.New,
+		encKeySize:   16,
+		macKeySize:   16,
+		tagSize:      16,
+		key:          key,
+	})
+}
+
+// NewAES192SHA384 returns an AEAD_AES_192_CBC_HMAC_SHA_384 instance given a
+// 48-byte key or an error if the key is the wrong size.
+// AEAD_AES_192_CBC_HMAC_SHA_384 combines AES-192 in CBC mode with
+// HMAC-SHA-384-192.
+func NewAES192SHA384(key []byte) (cipher.AEAD, error) {
+	return create(etmParams{
+		cipherParams: aesCBC,
+		macAlg:       sha512.New384,
+		encKeySize:   24,
+		macKeySize:   24,
+		tagSize:      24,
+		key:          key,
+	})
+}
+
+// NewAES256SHA384 returns an AEAD_AES_256_CBC_HMAC_SHA_384 instance given a
+// 56-byte key or an error if the key is the wrong size.
+// AEAD_AES_256_CBC_HMAC_SHA_384 combines AES-256 in CBC mode with
+// HMAC-SHA-384-192.
+func NewAES256SHA384(key []byte) (cipher.AEAD, error) {
+	return create(etmParams{
+		cipherParams: aesCBC,
+		macAlg:       sha512.New384,
+		encKeySize:   32,
+		macKeySize:   24,
+		tagSize:      24,
+		key:          key,
+	})
+}
+
+// NewAES256SHA512 returns an AEAD_AES_256_CBC_HMAC_SHA_512 instance given a
+// 64-byte key or an error if the key is the wrong size.
+// AEAD_AES_256_CBC_HMAC_SHA_512 combines AES-256 in CBC mode with
+// HMAC-SHA-512-256.
+func NewAES256SHA512(key []byte) (cipher.AEAD, error) {
+	return create(etmParams{
+		cipherParams: aesCBC,
+		macAlg:       sha512.New,
+		encKeySize:   32,
+		macKeySize:   32,
+		tagSize:      32,
+		key:          key,
+	})
+}
+
+// NewAES128SHA1 returns an AEAD_AES_128_CBC_HMAC_SHA1 instance given a 36-byte
+// key or an error if the key is the wrong size.
+// AEAD_AES_128_CBC_HMAC_SHA1 combines AES-128 in CBC mode with HMAC-SHA1-96.
+func NewAES128SHA1(key []byte) (cipher.AEAD, error) {
+	return create(etmParams{
+		cipherParams: aesCBC,
+		macAlg:       sha1.New,
+		encKeySize:   16,
+		macKeySize:   20,
+		tagSize:      12,
+		key:          key,
+	})
+}
+
+type etmParams struct {
+	cipherParams
+	encKeySize, macKeySize, tagSize int
+
+	key    []byte
+	macAlg func() hash.Hash
+}
+
+func create(p etmParams) (cipher.AEAD, error) {
+	l := p.encKeySize + p.macKeySize
+	if len(p.key) != l {
+		return nil, fmt.Errorf("etm: key must be %d bytes long", l)
+	}
+	encKey, macKey := split(p.key, p.encKeySize, p.macKeySize)
+	return &etmAEAD{
+		etmParams: p,
+		encKey:    encKey,
+		macKey:    macKey,
+	}, nil
+}
+
+const (
+	dataLenSize = 8
+)
+
+type etmAEAD struct {
+	etmParams
+	encKey, macKey []byte
+}
+
+func (aead *etmAEAD) Overhead() int {
+	return aead.padSize + aead.tagSize + dataLenSize + aead.NonceSize()
+}
+
+func (aead *etmAEAD) NonceSize() int {
+	return aead.nonceSize
+}
+
+func (aead *etmAEAD) Seal(dst, nonce, plaintext, data []byte) []byte {
+	b, _ := aead.encAlg(aead.encKey) // guaranteed to work
+
+	c := aead.encrypter(b, nonce)
+	i := aead.pad(plaintext, aead.blockSize)
+	s := make([]byte, len(i))
+	c.CryptBlocks(s, i)
+	s = append(nonce, s...)
+
+	t := tag(hmac.New(aead.macAlg, aead.macKey), data, s, aead.tagSize)
+
+	return append(dst, append(s, t...)...)
+}
+
+func (aead *etmAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
+	s := ciphertext[:len(ciphertext)-aead.tagSize]
+	t := ciphertext[len(ciphertext)-aead.tagSize:]
+	t2 := tag(hmac.New(aead.macAlg, aead.macKey), data, s, aead.tagSize)
+	if nonce == nil {
+		nonce = s[:aead.NonceSize()]
+	}
+
+	if !hmac.Equal(t, t2) {
+		return nil, errors.New("message authentication failed")
+	}
+
+	b, _ := aead.encAlg(aead.encKey) // guaranteed to work
+
+	c := aead.decrypter(b, nonce)
+	o := make([]byte, len(s)-len(nonce))
+	c.CryptBlocks(o, s[len(nonce):])
+
+	return append(dst, aead.unpad(o, aead.blockSize)...), nil
+}
+
+type cipherParams struct {
+	nonceSize, blockSize, padSize int
+
+	encAlg    func(key []byte) (cipher.Block, error)
+	encrypter func(cipher.Block, []byte) cipher.BlockMode
+	decrypter func(cipher.Block, []byte) cipher.BlockMode
+	pad       func([]byte, int) []byte
+	unpad     func([]byte, int) []byte
+}
+
+// AES-CBC-PKCS7
+var aesCBC = cipherParams{
+	encAlg:    aes.NewCipher,
+	blockSize: aes.BlockSize,
+	nonceSize: aes.BlockSize,
+	encrypter: cipher.NewCBCEncrypter,
+	decrypter: cipher.NewCBCDecrypter,
+	padSize:   aes.BlockSize,
+	pad:       pkcs7pad,
+	unpad:     pkcs7unpad,
+}
+
+func tag(h hash.Hash, data, s []byte, l int) []byte {
+	al := make([]byte, dataLenSize)
+	binary.BigEndian.PutUint64(al, uint64(len(data)*8)) // in bits
+	h.Write(data)
+	h.Write(s)
+	h.Write(al)
+	return h.Sum(nil)[:l]
+}
+
+func split(key []byte, encKeyLen, macKeyLen int) ([]byte, []byte) {
+	return key[0:encKeyLen], key[len(key)-macKeyLen:]
+}
+
+func pkcs7pad(b []byte, blockSize int) []byte {
+	ps := make([]byte, blockSize-(len(b)%blockSize))
+	for i := range ps {
+		ps[i] = byte(len(ps))
+	}
+	return append(b, ps...)
+}
+
+func pkcs7unpad(b []byte, blockSize int) []byte {
+	return b[:len(b)-int(b[len(b)-1])]
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 976997c9c..435d2aa0f 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -143,6 +143,9 @@ github.com/beorn7/perks/quantile
 github.com/bombsimon/wsl/v3
 # github.com/cespare/xxhash/v2 v2.1.1
 github.com/cespare/xxhash/v2
+# github.com/codahale/etm v0.0.0-20141003032925-c00c9e6fb4c9
+## explicit
+github.com/codahale/etm
 # github.com/containers/image v3.0.2+incompatible
 github.com/containers/image/docker/reference
 github.com/containers/image/pkg/sysregistriesv2


Test patch:
diff --git a/pkg/backend/openshiftcluster_test.go b/pkg/backend/openshiftcluster_test.go
index 271bcdb3f..7a51582a7 100644
--- a/pkg/backend/openshiftcluster_test.go
+++ b/pkg/backend/openshiftcluster_test.go
@@ -294,7 +294,7 @@ func TestBackendTry(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			createManager := func(context.Context, *logrus.Entry, env.Interface, database.OpenShiftClusters, encryption.Cipher, billing.Manager, *api.OpenShiftClusterDocument, *api.SubscriptionDocument) (cluster.Interface, error) {
+			createManager := func(context.Context, *logrus.Entry, env.Interface, database.OpenShiftClusters, encryption.AEAD, billing.Manager, *api.OpenShiftClusterDocument, *api.SubscriptionDocument) (cluster.Interface, error) {
 				return manager, nil
 			}
 
diff --git a/pkg/database/extensions_test.go b/pkg/database/extensions_test.go
index df87148ad..4288f3944 100644
--- a/pkg/database/extensions_test.go
+++ b/pkg/database/extensions_test.go
@@ -24,12 +24,12 @@ type testStruct struct {
 func TestExtensions(t *testing.T) {
 	ctx := context.Background()
 
-	cipher, err := encryption.NewXChaCha20Poly1305(ctx, []byte("\x63\xb5\x59\xf0\x43\x34\x79\x49\x68\x46\xab\x8b\xce\xdb\xc1\x2d\x7a\x0b\x14\x86\x7e\x1a\xb2\xd7\x3a\x92\x4e\x98\x6c\x5e\xcb\xe1"))
+	aead, err := encryption.NewXChaCha20Poly1305(ctx, []byte("\x63\xb5\x59\xf0\x43\x34\x79\x49\x68\x46\xab\x8b\xce\xdb\xc1\x2d\x7a\x0b\x14\x86\x7e\x1a\xb2\xd7\x3a\x92\x4e\x98\x6c\x5e\xcb\xe1"))
 	if err != nil {
 		t.Fatal(err)
 	}
 
-	h, err := NewJSONHandle(cipher)
+	h, err := NewJSONHandle(aead)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/pkg/frontend/admin_openshiftcluster_list_test.go b/pkg/frontend/admin_openshiftcluster_list_test.go
index 702ab2676..c2781e0f6 100644
--- a/pkg/frontend/admin_openshiftcluster_list_test.go
+++ b/pkg/frontend/admin_openshiftcluster_list_test.go
@@ -112,13 +112,13 @@ func TestAdminListOpenShiftCluster(t *testing.T) {
 				t.Fatal(err)
 			}
 
-			cipher := testdatabase.NewFakeCipher()
+			aead := testdatabase.NewFakeAEAD()
 
 			if tt.throwsError != nil {
 				ti.openShiftClustersClient.SetError(tt.throwsError)
 			}
 
-			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, cipher, nil)
+			f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, aead, nil)
 			if err != nil {
 				t.Fatal(err)
 			}
diff --git a/pkg/frontend/openshiftcluster_list_test.go b/pkg/frontend/openshiftcluster_list_test.go
index 81a3b02f6..5217e52ee 100644
--- a/pkg/frontend/openshiftcluster_list_test.go
+++ b/pkg/frontend/openshiftcluster_list_test.go
@@ -191,9 +191,9 @@ func TestListOpenShiftCluster(t *testing.T) {
 						ti.openShiftClustersClient.SetError(tt.dbError)
 					}
 
-					cipher := testdatabase.NewFakeCipher()
+					aead := testdatabase.NewFakeAEAD()
 
-					f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, cipher, nil)
+					f, err := NewFrontend(ctx, ti.log, ti.env, ti.asyncOperationsDatabase, ti.openShiftClustersDatabase, ti.subscriptionsDatabase, api.APIs, &noop.Noop{}, aead, nil)
 					if err != nil {
 						t.Fatal(err)
 					}
diff --git a/pkg/util/encryption/aescbc_test.go b/pkg/util/encryption/aescbc_test.go
new file mode 100644
index 000000000..1d5f6678d
--- /dev/null
+++ b/pkg/util/encryption/aescbc_test.go
@@ -0,0 +1,139 @@
+package encryption
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"bytes"
+	"context"
+	"encoding/hex"
+	"io"
+	"testing"
+)
+
+func TestNewAES256SHA512(t *testing.T) {
+	for _, tt := range []struct {
+		name    string
+		key     []byte
+		wantErr string
+	}{
+		{
+			name: "valid",
+			key:  make([]byte, 64),
+		},
+		{
+			name:    "key too short",
+			key:     make([]byte, 63),
+			wantErr: "etm: key must be 64 bytes long",
+		},
+		{
+			name:    "key too long",
+			key:     make([]byte, 65),
+			wantErr: "etm: key must be 64 bytes long",
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			_, err := NewAES256SHA512(context.Background(), tt.key)
+			if err != nil && err.Error() != tt.wantErr ||
+				err == nil && tt.wantErr != "" {
+				t.Fatal(err)
+			}
+		})
+	}
+}
+
+func TestAES256SHA512Open(t *testing.T) {
+	for _, tt := range []struct {
+		name       string
+		key        []byte
+		input      []byte
+		wantOpened []byte
+		wantErr    string
+	}{
+		{
+			name:       "valid",
+			key:        []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e\xab\x79\x86\x50\xfb\xce\xdc\x9d\xdd\x1c\x01\x32\xd6\x03\x99\xe6\x59\x81\x37\xb3\xdb\x67\x6f\x12\x34\x1d\xb9\x58\x18\x31\x30\x57"),
+			input:      []byte("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\xe0\x80\x26\x59\x2a\x20\xb2\xe5\x5e\x30\xd6\xd1\x24\x1e\x34\x36\xbe\xfb\x79\x8e\x46\xb5\x95\xce\xe0\x79\x9c\x44\x5c\xaa\x83\x26\x92\xdb\x76\x34\x33\xe0\x0e\x0e\x54\xb2\x0b\x2f\xde\x63\x53\xf6"),
+			wantOpened: []byte("test"),
+		},
+		{
+			name:    "invalid - encrypted value tampered with",
+			key:     []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e\xab\x79\x86\x50\xfb\xce\xdc\x9d\xdd\x1c\x01\x32\xd6\x03\x99\xe6\x59\x81\x37\xb3\xdb\x67\x6f\x12\x34\x1d\xb9\x58\x18\x31\x30\x57"),
+			input:   []byte("\xda\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\xe0\x80\x26\x59\x2a\x20\xb2\xe5\x5e\x30\xd6\xd1\x24\x1e\x34\x36\xbe\xfb\x79\x8e\x46\xb5\x95\xce\xe0\x79\x9c\x44\x5c\xaa\x83\x26\x92\xdb\x76\x34\x33\xe0\x0e\x0e\x54\xb2\x0b\x2f\xde\x63\x53\xf6"),
+			wantErr: "message authentication failed",
+		},
+		{
+			name:    "invalid - too short",
+			key:     []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e\xab\x79\x86\x50\xfb\xce\xdc\x9d\xdd\x1c\x01\x32\xd6\x03\x99\xe6\x59\x81\x37\xb3\xdb\x67\x6f\x12\x34\x1d\xb9\x58\x18\x31\x30\x57"),
+			input:   make([]byte, 31),
+			wantErr: "encrypted value too short",
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			cipher, err := NewAES256SHA512(context.Background(), tt.key)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			opened, err := cipher.Open(tt.input)
+			if err != nil && err.Error() != tt.wantErr ||
+				err == nil && tt.wantErr != "" {
+				t.Fatal(err)
+			}
+
+			if !bytes.Equal(tt.wantOpened, opened) {
+				t.Error(string(opened))
+			}
+		})
+	}
+}
+
+func TestAES256SHA512Seal(t *testing.T) {
+	for _, tt := range []struct {
+		name       string
+		key        []byte
+		randReader io.Reader
+		input      []byte
+		wantSealed []byte
+		wantErr    string
+	}{
+		{
+			name:       "valid",
+			key:        []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e\xab\x79\x86\x50\xfb\xce\xdc\x9d\xdd\x1c\x01\x32\xd6\x03\x99\xe6\x59\x81\x37\xb3\xdb\x67\x6f\x12\x34\x1d\xb9\x58\x18\x31\x30\x57"),
+			randReader: bytes.NewBufferString("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\x9c\x33\x5b\x16\xd6\x9a\x9c\xf2"),
+			input:      []byte("test"),
+			wantSealed: []byte("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\xe0\x80\x26\x59\x2a\x20\xb2\xe5\x5e\x30\xd6\xd1\x24\x1e\x34\x36\xbe\xfb\x79\x8e\x46\xb5\x95\xce\xe0\x79\x9c\x44\x5c\xaa\x83\x26\x92\xdb\x76\x34\x33\xe0\x0e\x0e\x54\xb2\x0b\x2f\xde\x63\x53\xf6"),
+		},
+		{
+			name:       "rand.Read EOF",
+			key:        make([]byte, 64),
+			randReader: &bytes.Buffer{},
+			wantErr:    "EOF",
+		},
+		{
+			name:       "rand.Read unexpected EOF",
+			key:        make([]byte, 64),
+			randReader: bytes.NewBufferString("X"),
+			wantErr:    "unexpected EOF",
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			cipher, err := NewAES256SHA512(context.Background(), tt.key)
+			if err != nil {
+				t.Fatal(err)
+			}
+
+			cipher.(*aes256Sha512).randReader = tt.randReader
+
+			sealed, err := cipher.Seal(tt.input)
+			if err != nil && err.Error() != tt.wantErr ||
+				err == nil && tt.wantErr != "" {
+				t.Fatal(err)
+			}
+
+			if !bytes.Equal(tt.wantSealed, sealed) {
+				t.Error(hex.EncodeToString(sealed))
+			}
+		})
+	}
+}
diff --git a/pkg/util/encryption/xchacha20poly1305_test.go b/pkg/util/encryption/xchacha20poly1305_test.go
index c109df94d..619ab5892 100644
--- a/pkg/util/encryption/xchacha20poly1305_test.go
+++ b/pkg/util/encryption/xchacha20poly1305_test.go
@@ -7,7 +7,7 @@ import (
 	"bytes"
 	"context"
 	"encoding/hex"
-	"fmt"
+	"io"
 	"testing"
 )
 
@@ -19,16 +19,16 @@ func TestNewXChaCha20Poly1305(t *testing.T) {
 	}{
 		{
 			name: "valid",
-			key:  []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+			key:  make([]byte, 32),
 		},
 		{
 			name:    "key too short",
-			key:     []byte("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
+			key:     make([]byte, 31),
 			wantErr: "chacha20poly1305: bad key length",
 		},
 		{
 			name:    "key too long",
-			key:     []byte("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"),
+			key:     make([]byte, 33),
 			wantErr: "chacha20poly1305: bad key length",
 		},
 	} {
@@ -42,19 +42,19 @@ func TestNewXChaCha20Poly1305(t *testing.T) {
 	}
 }
 
-func TestXChaCha20Poly1305Decrypt(t *testing.T) {
+func TestXChaCha20Poly1305Open(t *testing.T) {
 	for _, tt := range []struct {
-		name          string
-		key           []byte
-		input         []byte
-		wantDecrypted []byte
-		wantErr       string
+		name       string
+		key        []byte
+		input      []byte
+		wantOpened []byte
+		wantErr    string
 	}{
 		{
-			name:          "valid",
-			key:           []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e"),
-			input:         []byte("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\x9c\x33\x5b\x16\xd6\x9a\x9c\xf2\x9c\xf6\xe9\xbd\xdd\xe3\x1d\x54\xde\x41\xa2\x99\x56\x6a\xfc\x9a\xf3\x58\x73\x03"),
-			wantDecrypted: []byte("test"),
+			name:       "valid",
+			key:        []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e"),
+			input:      []byte("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\x9c\x33\x5b\x16\xd6\x9a\x9c\xf2\x9c\xf6\xe9\xbd\xdd\xe3\x1d\x54\xde\x41\xa2\x99\x56\x6a\xfc\x9a\xf3\x58\x73\x03"),
+			wantOpened: []byte("test"),
 		},
 		{
 			name:    "invalid - encrypted value tampered with",
@@ -65,74 +65,74 @@ func TestXChaCha20Poly1305Decrypt(t *testing.T) {
 		{
 			name:    "invalid - too short",
 			key:     []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e"),
-			input:   []byte("XXXXXXXXXXXXXXXXXXXXXXX"),
+			input:   make([]byte, 23),
 			wantErr: "encrypted value too short",
 		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
-			cipher, err := NewXChaCha20Poly1305(context.Background(), tt.key)
+			aead, err := NewXChaCha20Poly1305(context.Background(), tt.key)
 			if err != nil {
 				t.Fatal(err)
 			}
 
-			decrypted, err := cipher.Decrypt(tt.input)
+			opened, err := aead.Open(tt.input)
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Fatal(err)
 			}
 
-			if !bytes.Equal(tt.wantDecrypted, decrypted) {
-				t.Error(string(decrypted))
+			if !bytes.Equal(tt.wantOpened, opened) {
+				t.Error(string(opened))
 			}
 		})
 	}
 }
 
-func TestXChaCha20Poly1305Encrypt(t *testing.T) {
+func TestXChaCha20Poly1305Seal(t *testing.T) {
 	for _, tt := range []struct {
-		name          string
-		key           []byte
-		randRead      func(b []byte) (int, error)
-		input         []byte
-		wantEncrypted []byte
-		wantErr       string
+		name       string
+		key        []byte
+		randReader io.Reader
+		input      []byte
+		wantSealed []byte
+		wantErr    string
 	}{
 		{
-			name: "valid",
-			key:  []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e"),
-			randRead: func(b []byte) (int, error) {
-				nonce := []byte("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\x9c\x33\x5b\x16\xd6\x9a\x9c\xf2")
-				copy(b, nonce)
-				return len(nonce), nil
-			},
-			input:         []byte("test"),
-			wantEncrypted: []byte("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\x9c\x33\x5b\x16\xd6\x9a\x9c\xf2\x9c\xf6\xe9\xbd\xdd\xe3\x1d\x54\xde\x41\xa2\x99\x56\x6a\xfc\x9a\xf3\x58\x73\x03"),
+			name:       "valid",
+			key:        []byte("\x6a\x98\x95\x6b\x2b\xb2\x7e\xfd\x1b\x68\xdf\x5c\x40\xc3\x4f\x8b\xcf\xff\xe8\x17\xc2\x2d\xf6\x40\x2e\x5a\xb0\x15\x63\x4a\x2d\x2e"),
+			randReader: bytes.NewBufferString("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\x9c\x33\x5b\x16\xd6\x9a\x9c\xf2"),
+			input:      []byte("test"),
+			wantSealed: []byte("\xd9\x1c\x3c\x05\xb2\xf3\xc5\x93\x20\x9f\x9b\x67\x43\x8c\x0c\x3d\x9c\x33\x5b\x16\xd6\x9a\x9c\xf2\x9c\xf6\xe9\xbd\xdd\xe3\x1d\x54\xde\x41\xa2\x99\x56\x6a\xfc\x9a\xf3\x58\x73\x03"),
+		},
+		{
+			name:       "rand.Read EOF",
+			key:        make([]byte, 32),
+			randReader: &bytes.Buffer{},
+			wantErr:    "EOF",
 		},
 		{
-			name: "rand.Read error",
-			key:  []byte("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
-			randRead: func(b []byte) (int, error) {
-				return 0, fmt.Errorf("random error")
-			},
-			wantErr: "random error",
+			name:       "rand.Read unexpected EOF",
+			key:        make([]byte, 32),
+			randReader: bytes.NewBufferString("X"),
+			wantErr:    "unexpected EOF",
 		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
-			cipher, err := NewXChaCha20Poly1305(context.Background(), tt.key)
+			aead, err := NewXChaCha20Poly1305(context.Background(), tt.key)
 			if err != nil {
 				t.Fatal(err)
 			}
 
-			cipher.(*aeadCipher).randRead = tt.randRead
+			aead.(*xChaCha20Poly1305).randReader = tt.randReader
 
-			encrypted, err := cipher.Encrypt(tt.input)
+			sealed, err := aead.Seal(tt.input)
 			if err != nil && err.Error() != tt.wantErr ||
 				err == nil && tt.wantErr != "" {
 				t.Fatal(err)
 			}
 
-			if !bytes.Equal(tt.wantEncrypted, encrypted) {
-				t.Error(hex.EncodeToString(encrypted))
+			if !bytes.Equal(tt.wantSealed, sealed) {
+				t.Error(hex.EncodeToString(sealed))
 			}
 		})
 	}
diff --git a/test/database/cipher.go b/test/database/aead.go
similarity index 57%
rename from test/database/cipher.go
rename to test/database/aead.go
index 1a667bc06..a6dbb3122 100644
--- a/test/database/cipher.go
+++ b/test/database/aead.go
@@ -5,20 +5,19 @@ package database
 
 var fakeCode []byte = []byte{'F', 'A', 'K', 'E'}
 
-type fakeCipher struct {
-}
+type fakeAEAD struct{}
 
-func (c fakeCipher) Decrypt(in []byte) ([]byte, error) {
+func (fakeAEAD) Open(in []byte) ([]byte, error) {
 	return in[4:], nil
 }
 
-func (c fakeCipher) Encrypt(in []byte) ([]byte, error) {
+func (fakeAEAD) Seal(in []byte) ([]byte, error) {
 	out := make([]byte, 4+len(in))
 	copy(out, fakeCode)
 	copy(out[4:], in)
 	return out, nil
 }
 
-func NewFakeCipher() *fakeCipher {
-	return &fakeCipher{}
+func NewFakeAEAD() *fakeAEAD {
+	return &fakeAEAD{}
 }
diff --git a/test/database/cipher_test.go b/test/database/aead_test.go
similarity index 64%
rename from test/database/cipher_test.go
rename to test/database/aead_test.go
index 7e5f3a041..b09b40610 100644
--- a/test/database/cipher_test.go
+++ b/test/database/aead_test.go
@@ -5,15 +5,15 @@ package database
 
 import "testing"
 
-func TestFakeCipher(t *testing.T) {
-	c := &fakeCipher{}
+func TestFakeAEAD(t *testing.T) {
+	c := &fakeAEAD{}
 
-	encrypted, _ := c.Encrypt([]byte{'f', 'o', 'o'})
+	encrypted, _ := c.Seal([]byte{'f', 'o', 'o'})
 	if string(encrypted) != "FAKEfoo" {
 		t.Error(string(encrypted))
 	}
 
-	decrypted, _ := c.Decrypt(encrypted)
+	decrypted, _ := c.Open(encrypted)
 	if string(decrypted) != "foo" {
 		t.Error(string(decrypted))
 	}
diff --git a/test/database/inmemory.go b/test/database/inmemory.go
index fd3c53bfe..0365c9bf9 100644
--- a/test/database/inmemory.go
+++ b/test/database/inmemory.go
@@ -14,7 +14,7 @@ var jsonHandle *codec.JsonHandle
 
 func init() {
 	var err error
-	jsonHandle, err = database.NewJSONHandle(&fakeCipher{})
+	jsonHandle, err = database.NewJSONHandle(&fakeAEAD{})
 	if err != nil {
 		panic(err)
 	}