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
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.
-
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
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
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
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
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"
}
]
}
}
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