tink-crypto / tink

Tink is a multi-language, cross-platform, open source library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.
https://developers.google.com/tink
Apache License 2.0
13.47k stars 1.18k forks source link

Envelope AEAD with multiple KEKs and AEAD primitives #659

Closed iamyohann closed 1 year ago

iamyohann commented 1 year ago

I have a general question around best practices when dealing with multiple KEKs for envelope encryption.

Background We have a centralised security solution that handles encryption for various tenants. Each tenant has 1 GCP KMS Key dedicated to it. The purpose of having separate KMS Keys for each tenant is to ensure secure data isolation and limit risk in events where the key is compromised etc.. etc...

Scenario After securely identifying the tenant during a request, the centralised security solution picks the appropriate KEK (remote GCP KMS key) to process the request (i.e. encrypt the data). Specifically, it picks the right AEAD primitive instance that matches the tenant's KEK.

With the Google Tink Library, the example docs to generate an AEAD primitive is:

kekURI := "gcp-kms://.../.../.../my-cloud-kek-123"
kmsClient, err := gcpkms.NewClient(kekURI)
if err != nil {
...
}

registry.RegisterKMSClient(kmsClient)

dek := aead.AES256GCMKeyTemplate()
keyHandle, err := keyset.Handle(aead.KMSEnvelopeAEADKeyTemplate(kekURI)
if err != nil {
...
}

aeadPrimitive, err := aead.New(keyHandle)
if err != nil {
...
}

// We can finally use the AEAD primitive here for envelope encryption for a single KEK
aead.Encrypt(....)
aead.Decrypt(....)

Now the code example above works fine for a single KEK, however we'd like to support multiple KEKs and be able to dynamically switch KEKs depending on the requests the "centralised security solution" receives for each tenant.

N tenants = N KEK (GCP KMS keys)

Note: The list of tenants can grow over time, its not a static number (N).

The "centralised security solution" can receive concurrent requests (gRPC server) and must be able to handle concurrent requests without issues.

Our initial approach to the problem is shown below:

type TenantAEADCache struct {
    mu sync.RWMutex

    cache map[string]tink.AEAD
}

func (t *TenantAEADCache) GetAEADPrimitive(tenant string) (tink.AEAD, error) {
    aeadPrimitive, err := t.GetAEADPrimitiveFromCache(tenant)
    if err != nil {
        return t.GenerateAndGetAEADPrimitive(tenant)

    }
    return aeadPrimitive, err
}

func (t *TenantAEADCache) GetAEADPrimitiveFromCache(tenant string) (tink.AEAD, error) {
    // read lock
    t.mu.RLock()
    defer t.mu.RLock()

    aeadPrimitive, ok := t.cache[tenant]
    if !ok {
        return nil, errors.New("not cached")
    }
    return aeadPrimitive, nil
}

func (t *TenantAEADCache) GenerateAndGetAEADPrimitive(tenant string) (tink.AEAD, error) {
    // write lock
    t.mu.Lock()
    defer t.mu.Lock()

    t.cache[tenant] = .... // generate an AEADPrimitive in a similar way to the first code example at the top

    return t.cache[tenant], nil
}

(please ignore any minor code issues, this was not a copy pasted snippet, just a best effort manually typed example)

I'd like to learn about:

I've read through a bit of the code for KMSEnvelopeAEAD which seems to be the instance/concrete type of tink.AEAD that I'm working with. Would there be any locks or concurrency issues with the code below

https://github.com/google/tink/blob/master/go/aead/kms_envelope_aead.go#L59-L129

Alternatively, please let me know if there's a better approach to solving concurrent multi-tenant (multi KEK) envelope encryption with Google Tink.

Regards

iamyohann commented 1 year ago

@chuckx

juergw commented 1 year ago

I don't think that there would be concurrency issues. But note that the envelope encryption will always do a remove RPC call whenever you call encrypt or decrypt. And that will probably be more costly then creating the aead object. So I think caching these objects will not help much.

juergw commented 1 year ago

Maybe I missunderstood the question, your code is not clear to me. You don't seem to use the "dek" that you created. And you call "aead.Encrypt", which doesn't work because "aead" is the package and not the primitive. I guess that would be "aeadPrimitve.Encrypt"?

I think you should not do it as KMSEnvelopeAEAD does it. Because that primitive always stores a new key with everything that gets encrypted. So adding caching to this only possible to a certain extend, and I would not recommend it.

It's better to do it like this:

The example on how to use encrypted keyset is here: https://developers.google.com/tink/generate-encrypted-keyset

I hope that helps.