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.5k stars 1.18k forks source link

Envelope AEAD Data Key Generation #618

Closed iamyohann closed 2 years ago

iamyohann commented 2 years ago

I'd like to confirm a few things about data key generation during envelope encryption.

Based on the envelope example in the documentation here: https://github.com/google/tink/blob/master/docs/GOLANG-HOWTO.md#envelope-encryption

package main

import (
  "encoding/base64"
  "fmt"

  "github.com/google/tink/go/aead"
  "github.com/google/tink/go/core/registry"
  "github.com/google/tink/go/integration/gcpkms"
  "github.com/google/tink/go/keyset"
)

const (
   // Change this. AWS KMS, Google Cloud KMS and HashiCorp Vault are supported out of the box.
   keyURI          = "gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar"
   credentialsPath = "credentials.json"
)

func main() {
  gcpclient, err := gcpkms.NewClientWithCredentials(keyURI, credentialsPath)
  if err != nil {
    log.Fatal(err)
  }
  registry.RegisterKMSClient(gcpclient)

  dek := aead.AES128CTRHMACSHA256KeyTemplate()
  kh, err := keyset.NewHandle(aead.KMSEnvelopeAEADKeyTemplate(keyURI, dek))
  if err != nil {
    log.Fatal(err)
  }

  a, err := aead.New(kh)
  if err != nil {
    log.Fatal(err)
  }

  msg := []byte("this message needs to be encrypted")
  aad := []byte("this data needs to be authenticated, but not encrypted")
  ct, err := a.Encrypt(msg, aad)
  if err != nil {
    log.Fatal(err)
  }

  pt, err := a.Decrypt(ct, aad)
  if err != nil {
    log.Fatal(err)
  }

  fmt.Printf("Ciphertext: %s\n", base64.StdEncoding.EncodeToString(ct))
  fmt.Printf("Original  plaintext: %s\n", msg)
  fmt.Printf("Decrypted Plaintext: %s\n", pt)
}

The example generates a key handle kh and a AEAD primitive a (via aead.New)

The docs on envelope encryption don't go into much detail on DEK generation.

Could we confirm if a new DEK is generated on each call to the same instance of a.Encrypt? (i.e. no need to re-instantiate AEAD primitive or key handle/key set over and over again)

If thats the case, I presume:

I've looked through some of the Tink library code, specifically these lines: https://github.com/google/tink/blob/master/go/aead/kms_envelope_aead.go#L58-L71

// Encrypt implements the tink.AEAD interface for encryption.
func (a *KMSEnvelopeAEAD) Encrypt(pt, aad []byte) ([]byte, error) {
    dekM, err := registry.NewKey(a.dekTemplate)
    if err != nil {
        return nil, err
    }
    dek, err := proto.Marshal(dekM)
    if err != nil {
        return nil, err
    }
    encryptedDEK, err := a.remote.Encrypt(dek, []byte{})
    if err != nil {
        return nil, err
    }

And it definitely looks like its generating a separate data key on each a.Encrypt call (on the same AEAD primitive instance). Using GCP KMS, I was able to debug and see it calling KMS Encrypt API when remote.Encrypt was called, so it does look like its generating DEKs on the fly (and encrypting them via KMS API for each call), through a single instance of the AEAD Primitive (aead.New(...), i.e. no need to re-instantiate)

I raised this issue to double check the approach to ensure that we have separate DEK's embedded per cipher text generated.

Regards

chuckx commented 2 years ago

Envelope encryption is also discussed here (and we're generally migrating more and more documentation to this site): https://developers.google.com/tink/client-side-encryption.

On that page there's a callout that speaks to your concern specifically:

Note: Tink's envelope encryption generates a unique DEK for each message you encrypt...

As you found in the code snippet you posted, Tink generates a unique data encryption key (DEK) upon every call to KMSEnvelopeAEAD.Encrypt().

Then, as you can see in the wire format documentation, the DEK is embedded alongside the ciphertext. Then, during decryption, the DEK is extracted and decrypted via a KMS decrypt call, as you see in the implementation of KMSEnvelopeAEAD.Decrypt().