salrashid123 / tink-keyset-util

Export and Import external keys for TINK crypto

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Tink Keyset Key Utility

Utility function to extract or export the embedded AES, RSA, ECC or HMAC key inside a tink-crypto cleartext or encrypted keyset.

You can also use this library to import an external key into a tink keyset.

This repo also also allows for a way to remove the prefix added by Tink to most ciphertext data generated by Tink.

Using both these functions will allow you to encrypt or sign some data with Tink and use off the shelf libraries to decrypt/verify later.

Basically, this is a way to remove the extra bits tink uses to make off-the-shelf compatibility a bit less challenging.

  • Export Key

For key extraction, consider the following AESGCM keyset:

$ cat keysets/aes_gcm_1.json | jq '.'
{
  "primaryKeyId": 1651423683,
  "key": [
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "GiCL0DVayMOEPOt/vw76hVAFNmOqFcxQ3RCBiX1u8yy5FA==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "ENABLED",
      "keyId": 1651423683,
      "outputPrefixType": "TINK"
    }
  ]
}

The value isn't the raw AESGCM Key but its actually the AesGcmKey proto.

This sample will decode the proto and show the raw encryption key which you can directly use with off the shelf crypto/aes library

$ go run aes_export/insecurekeyset/main.go --insecure-key-set keysets/aes_gcm_1.bin 
		Raw key: i9A1WsjDhDzrf78O+oVQBTZjqhXMUN0QgYl9bvMsuRQ=

This utility only supports both binary and json keysets. For an example with json keysets, see example/aes_export/insecurejsonkeyset folder


  • Extract CipherText

If you use TINK to encrypt any data, then the ciphertext can have various prefixes added in by Tink. Which means even with the raw key, the ciphertext wont decrypt. These prefixes are descried in TINK wire format prefixes

This library will detect the output type declared in the keyset, then remove the prefix value if outputPrefix=TINK was set using the primary KEYID so you can decrypt easily.

For an end-to-end example with AESGCM, see example/aes_export/insecurekeyset/main.go


  • Import Key

You can also use this library to embed an external AES-GCM key into a Tink insecure or encrypted keyset. In other words, if you have a raw aes gcm key, you can embed that into a TINK keyset.

In other words, if you already have an AES-GCM key, you can use this library to create a tink keyset with that key. see example/aes_import/insecurekeyset/main.go


This repo is a generic implementation of


Key export

The key types supported are:

  • ExportAesGcmKey()

    Extract the raw AES-GCM key from the keyset. You can use this key to decrypt/encrypt data using standard google AES library

  • ExportAesSivKey()

    Extract the raw AES-SIV key from the keyset. You can use this key to decrypt/encrypt data using standard google AES library

  • ExportAesCtrHmacAeadKey()

    Extract the raw AES and HMAC key from the keyset. Using off the shelf libraries requires reversing this process.

  • ExportRsaSsaPkcs1PrivateKey()

    Extract the RSA Private key from the keyset as DER bytes.

  • ExportRsaSsaPkcs1PublicKey()

    Extract the RSA Public key from the keyset as DER bytes.

  • ExportEcdsaPrivateKey()

    Extract the ECC Private key from the keyset as DER bytes.

  • ExportEcdsaPublicKey()

    Extract the ECC Public key from the keyset as DER bytes.

  • ExportHMACKey()

    Extract the HMAC the keyset.

To process TINK encoded ciphertext or data

  • ExportCipherText()

    Returns the ciphertext or signature without the TINK prefix values.

    You can use this output with off the shelf crypto libraries to decrypt or verify.

Key Import

  • ImportSymmetricKey()

    Supply the raw aes key, the keyID to use and the output prefix to apply for this keyset

    If an external KMS KEK is provided, the output will be an encryptedKeySet

  • ImportHMACKey()

    Unimplemented but easy to do. see tink_samples/external_hmac

see the example/ folder for details


this library is NOT supported by google


Usage

For key extraction supply the keyset.

	// load the keyset
	keysetBytes, err := os.ReadFile(*insecureKeySetFile)

	ku, err := keysetutil.NewTinkKeySetUtil(ctx, &keysetutil.KeySetUtilConfig{
		KeySetBytes: keysetBytes,
	})

	// print the raw key
	rk, err := ku.ExportAesGcmKey(keysetHandle.KeysetInfo().PrimaryKeyId)
	log.Printf("Raw key: %s", base64.StdEncoding.EncodeToString(rk))

For prefix redaction, supply the ciphertext provided by a prior tink operation.

	// load the keyset
	keysetBytes, err := os.ReadFile(*insecureKeySetFile)
	keysetReader := keyset.NewBinaryReader(bytes.NewReader(keysetBytes))
	keysetHandle, err := insecurecleartextkeyset.Read(keysetReader)   
	a, err := aead.New(keysetHandle)

	// use tink to encrypt
	ec, err := a.Encrypt([]byte("foo"), []byte("some additional data"))   

	// initialize this library
	ku, err := keysetutil.NewTinkKeySetUtil(ctx, &keysetutil.KeySetUtilConfig{
		KeySetBytes: keysetBytes,
	})

	// get the raw key from the keyset
	rk, err := ku.ExportAesGcmKey(keysetHandle.KeysetInfo().PrimaryKeyId)
	log.Printf("Raw key: %s", base64.StdEncoding.EncodeToString(rk))

	// initialize aes cipher from this extracted key
	aesCipher, err := aes.NewCipher(rk)
	rawAES, err := cipher.NewGCM(aesCipher)

	// omit the ciphertext prefix
	ecca, err := ku.ExportCipherText(ec, keysetHandle.KeysetInfo().PrimaryKeyId)

	// decrypt the tinkencrypted data using the raw ciphertext and raw aes key
	plaintext, err := rawAES.Open(nil, ecca[:keysetutil.AESGCMIVSize], ecca[keysetutil.AESGCMIVSize:], []byte("some additional data"))

THe following uses tinkey to create binary keysets and then extract out the embedded keys.

You can either use the existing keysets or generate your own using tinkey. For encrypted keysets, you certainly need to generate your own keysets.

for reference also see

Insecure KeySet

  • AES256_GCM
$ cd example

$ tinkey list-key-templates

## if you want to create new keysets to test with:
# $ tinkey create-keyset --key-template=AES256_GCM --out-format=binary --out=keysets/aes_gcm_1.bin
# $ tinkey convert-keyset --in-format=binary --in=keysets/aes_gcm_1.bin --out-format=json --out=keysets/aes_gcm_1.json

## this repo could some sample keysets...so to use existing keyset with this repo
# $ tinkey list-keyset --in-format=binary --in=keysets/aes_gcm_1.bin

$ go run aes_export/insecurekeyset/main.go --insecure-key-set keysets/aes_gcm_1.bin
  • AES256_GCM_RAW
# $ tinkey create-keyset --key-template=AES256_GCM_RAW --out-format=binary --out=keysets/aes_gcm_raw.bin

$ go run aes_export/insecurekeyset/main.go --insecure-key-set keysets/aes_gcm_raw.bin
  • AES256_SIV
# $ tinkey create-keyset --key-template=AES256_SIV --out-format=binary --out=keysets/aes_siv.bin

$ go run aes_siv/insecurekeyset/main.go --insecure-key-set keysets/aes_siv.bin 
  • AES256_CTR_HMAC_SHA256
# $ tinkey create-keyset --key-template=AES256_CTR_HMAC_SHA256 --out-format=binary --out=keysets/aes_ctr_hmac_sha256.bin

$ go run aes_ctr/insecurekeyset/main.go --insecure-key-set keysets/aes_ctr_hmac_sha256.bin 
  • RSA_SSA_PKCS1_3072_SHA256_F4
# $ tinkey create-keyset --key-template=RSA_SSA_PKCS1_3072_SHA256_F4 --out-format=binary --out=keysets/rsa_1_private.bin
# $ tinkey create-public-keyset --in-format=binary --in=keysets/rsa_1_private.bin --out-format=binary --out=keysets/rsa_1_public.bin

$ go run rsa/insecurekeyset/main.go --insecure-key-set keysets/rsa_1_private.bin 
$ go run rsa/insecurekeyset/main.go --insecure-key-set keysets/rsa_1_public.bin 
  • ECDSA_P256
# $ tinkey create-keyset --key-template=ECDSA_P256 --out-format=binary --out=keysets/ecc_1_private.bin
# $ tinkey create-public-keyset --in=keysets/ecc_1_private.bin --in-format=binary --out-format=binary --out=keysets/ecc_1_public.bin

$ go run ecc/insecurekeyset/main.go --insecure-key-set keysets/ecc_1_private.bin 
$ go run ecc/insecurekeyset/main.go --insecure-key-set keysets/ecc_1_public.bin 
  • HMAC_SHA256_256BITTAG
# $ tinkey create-keyset --key-template=HMAC_SHA256_256BITTAG --out-format=binary --out=keysets/hmac_bittag.bin

$ go run hmac_export/main.go --insecure-key-set keysets/hmac_bittag.bin
  • HMAC_SHA256_256BITTAG_RAW
# $ tinkey create-keyset --key-template=HMAC_SHA256_256BITTAG_RAW --out-format=binary --out=keysets/hmac_bittag_raw.bin

$ go run hmac_export/main.go --insecure-key-set keysets/hmac_bittag_raw.bin

Encrypted KeySet

To test encrypted keysets, you need to have access to a KMS and have to create new keysets from scratch (since they're KMS encrypted with your key)

$ export PROJECT_ID=`gcloud config get-value core/project`
$ gcloud kms keyrings create kr1 --location=global
$ gcloud kms keys create --keyring=kr1 --location=global --purpose=encryption  k1

$ gcloud auth application-default login

$ export MASTERKEY="gcp-kms://projects/$PROJECT_ID/locations/global/keyRings/kr1/cryptoKeys/k1"

# $ tinkey create-keyset --master-key-uri=$MASTERKEY --key-template=AES256_GCM --out-format=binary --out=keysets/aes_gcm_1_kms.bin

$ go run aes_export/encryptedkeyset/main.go --encrypted-key-set keysets/aes_gcm_1_kms.bin --master-key-uri=$MASTERKEY

Importing existing AES Key

To import an external AES_GCM key is pretty simple:

The following will create an AES_GCM Tink Keyset with the specified key, keyid and output prefix.

The return value is JSON keyset byte which you can convert to a JSON or Binary keyset for persistence.

(see example/aes_import/insecurekeyset/main.go for examples of other importable keytypes)

	keyValue := "9d17bL1kuWVfEfn9skFI7Caost/X/Qf1/Wafl14gyGQ="
	keyid := 4112199248

	// aes-gcm
	k := gcmpb.AesGcmKey{
		Version:  0,
		KeyValue: kval,
	}

	ek, err := keysetutil.CreateSymmetricKey(k, uint32(*keyid), tinkpb.OutputPrefixType_TINK, nil)
$ go run aes_import/insecurekeyset/main.go 
2024/04/25 22:49:51 Tink Keyset:
 {
	"primaryKeyId": 4112199248,
	"key": [
		{
			"keyData": {
				"typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
				"value": "GiD13XtsvWS5ZV8R+f2yQUjsJqiy39f9B/X9Zp+XXiDIZA==",
				"keyMaterialType": "SYMMETRIC"
			},
			"status": "ENABLED",
			"keyId": 4112199248,
			"outputPrefixType": "TINK"
		}
	]
}
2024/04/25 22:49:51 Tink Decrypted: foo

  • Encrypted KeySet

For an encrypted keyset, supply the kek aead:

	gcpClient, err := gcpkms.NewClientWithOptions(ctx, "gcp-kms://")
	kmsaead, err := gcpClient.GetAEAD(*kmsURI)

	ek, err := keysetutil.CreateSymmetricKey(k, uint32(*keyid), tinkpb.OutputPrefixType_TINK, kmsaead)
$ go run aes_import/encryptedkeyset/main.go --master-key-uri=$MASTERKEY

2024/04/25 22:24:20 Tink Keyset:
 {
	"encryptedKeyset": "CiQAhitNP4eOsQPhMlF5W9YX4xM3PFl9r/UrmRl3zeqhEFcG+UoSSwCFB1VVAs6MzdRyQmkQm8mLlwkvv0z4cCPozxOUkx85IYqx+mnfwABE4yA7e7gIjIdQdf9kuUvydrKC+mjeD7TpgL9wNSPePRTcOg==",
	"keysetInfo": {
		"primaryKeyId": 4112199248,
		"keyInfo": [
			{
				"typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
				"status": "ENABLED",
				"keyId": 4112199248,
				"outputPrefixType": "TINK"
			}
		]
	}
}

Importing existing HMAC Key

The following will create an HMAC Tink Keyset with the specified key, keyid and output prefix.

The return value is JSON keyset byte which you can convert to a JSON or Binary keyset for persistence.

We are using raw outputprefix so so that we can easily see the hmac value is what we expect

	key        = flag.String("key", "change this password to a secret", "raw key")
	keyid      = flag.Uint("keyid", 4112199248, "raw key")
	plaintText = flag.String("plaintText", "foo", "some data to mac")

	ek, err := keysetutil.CreateHMACKey([]byte(*key), uint32(*keyid), common_go_proto.HashType_SHA256, tinkpb.OutputPrefixType_RAW, nil)
$ go run hmac_import/main.go 
Tink Keyset:
 {
	"primaryKeyId": 4112199248,
	"key": [
		{
			"keyData": {
				"typeUrl": "type.googleapis.com/google.crypto.tink.HmacKey",
				"value": "EgQIAxAgGiBjaGFuZ2UgdGhpcyBwYXNzd29yZCB0byBhIHNlY3JldA==",
				"keyMaterialType": "SYMMETRIC"
			},
			"status": "ENABLED",
			"keyId": 4112199248,
			"outputPrefixType": "RAW"
		}
	]
}

HMAC: 7c50506d993b4a10e5ae6b33ca951bf2b8c8ac399e0a34026bb0ac469bea3de2

About

Export and Import external keys for TINK crypto

License:Apache License 2.0


Languages

Language:Go 100.0%