Transform sensitive data with Vault
Note
The Transform secrets engine requires a Vault Enterprise Advanced Data Protection (ADP) license or HCP Vault Dedicated Plus tier cluster.
Challenge
Vault's Transit secrets engine provides encryption service; however, the resulting ciphertext does not preserve the original data format or length.
![Transit Secrets Engine](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Fvault-transit.png%26width%3D1461%26height%3D425&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
Think of a scenario where an organization must cryptographically protect personally identifiable information (PII) while preserving the data format and length. For example, the database schema expects a certain character length and/or only allows alphanumeric characters.
The preservation of the original data format or length may be driven by compliance with certain industry standards such as HIPAA or PCI.
Solution
Vault Enterprise 1.4 with Advanced Data Protection module introduced the Transform secrets engine to handle secure data transformation and tokenization against provided secrets. Transformation methods encompass NIST vetted cryptographic standards such as format-preserving encryption (FPE) via FF3-1 to encode your secrets while maintaining the data format and length. In addition, it can also perform pseudonymous transformations of the data through other means, such as masking.
![Transform Secrets Engine](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Ftransform%252Fvault-transform.png%26width%3D1484%26height%3D467&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
This prevents the need for change in your existing database schemas.
Prerequisites
To perform the tasks described in this tutorial, you need to have Vault Enterprise with the Advanced Data Protection module or a HCP Vault Dedicated Plus tier cluster.
- Access to a Vault Enterprise license with the ADP module to run Vault in dev mode. If you do not have a license you can request one from your customer success team or use an HCP Vault Dedicated Plus tier cluster.
- jq installed
Policy requirements
For the purpose of this tutorial, you can use root
token to work
with Vault. However, it is recommended that root tokens are only used for just
enough initial setup or in emergencies. As a best practice, use tokens with
appropriate set of policies based on your role in the organization.
To perform all tasks demonstrated in this tutorial, your policy must include the following permissions:
# Work with transform secrets engine
path "transform/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
# Enable secrets engine
path "sys/mounts/*" {
capabilities = [ "create", "read", "update", "delete", "list" ]
}
# List enabled secrets engine
path "sys/mounts" {
capabilities = [ "read", "list" ]
}
If you are not familiar with policies, complete the policies tutorial.
Lab setup
Export an environment variable with a valid Vault Enterprise license.
$ export VAULT_LICENSE=02MV4UU43BK5....
Open a terminal and start a Vault dev server using the Vault Enterprise binary with
root
as the root token.$ vault server -dev -dev-root-token-id root
The Vault dev server defaults to running at
127.0.0.1:8200
. The server is initialized and unsealed.Insecure operation
Do not run a Vault dev server in production. This approach starts a Vault server with an in-memory database and runs in an insecure way.
Export an environment variable for the
vault
CLI to address the Vault server.$ export VAULT_ADDR=http://127.0.0.1:8200
Export an environment variable for the
vault
CLI to authenticate with the Vault server.$ export VAULT_TOKEN=root
Note
For these tasks, you can use Vault's root token. However, it is recommended that root tokens are only used for enough initial setup or in emergencies. As a best practice, use an authentication method or token that meets the policy requirements.
The Vault server is ready, you are ready to proceed with the lab.
Transform secrets engine workflow
Transform secrets engine configuration workflow:
- Enable the
transform
secrets engine - Create a role containing the transformations that it can perform
- Create an alphabet defining a set of characters to use for format-preserving encryption (FPE) if not using the built-in alphabets.
- Create a template defining the rules for value matching if not using the built-in template
- Create a transformation to specify the nature of the data manipulation
![Relationship](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Ftransform%252Fvault-transform-2.png%26width%3D1284%26height%3D578&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
Alphabets define a set of valid input/output UTF-8 characters to be used when you perform FPE. In this step, you are going to leverage one of the built-in alphabets. Read the create custom alphabets section to learn how to define your own alphabets.
Data transformation templates are constructed of type (regex
),
pattern (regular
expression) and allowed
alphabet used in the input value. Currently, regex is the only supported
type. The pattern defines the data format pattern. For example, the most credit
card numbers would have a pattern that can be expressed as
(\d{4})-(\d{4})-(\d{4})-(\d{4})
in regex.
In this step, the use of the builtin/creditcardnumber
template is
demonstrated. Read the create custom templates
section to learn how to define your own templates.
Transformations define the transformation template, tweak source or the masking character to be used to transform the secrets.
Tweak source types:
Source | Description |
---|---|
supplied (default) | User provide the tweak source which must be a base64-encoded 7-digit string |
generated | Vault generates and returns the tweak source along with the encoded data. The user must securely store the tweak source which will be needed to decrypt the data |
internal | Vault generates a tweak source for the transformation and the same tweak source will be used for every request |
Note
Tweak source is only applicable to the FPE transformation.
Create a new transformation
In this section, you are going to:
- Enable the transform secrets engine
- Create a payments role which includes card-number transformation
- Create a card-number transformation which performs format preserving encryption
![Scenario](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Ftransform%252Fvault-transform-0.png%26width%3D1771%26height%3D407&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
Enable the
transform
secrets engine attransform/
.$ vault secrets enable transform Success! Enabled the transform secrets engine at: transform/
Create a role named "payments" with "card-number" transformation attached which you will create next.
$ vault write transform/role/payments transformations=card-number Success! Data written to: transform/role/payments
List the existing roles.
$ vault list transform/role Keys ---- payments
Create a transformation named "card-number" which will be used to transform credit card numbers. This uses the built-in template
builtin/creditcardnumber
to perform format-preserving encryption (FPE). The allowed role to use this transformation ispayments
, which you just created.$ vault write transform/transformations/fpe/card-number \ template="builtin/creditcardnumber" \ tweak_source=internal \ allowed_roles=payments
Example Output:
Success! Data written to: transform/transformations/fpe/card-number
Note
The
allowed_roles
parameter can be set to a wildcard (*
) instead of listing role names. Also, the role name can be expressed using globs at the end for pattern matching (e.g.pay*
).You will learn how to define your own template in the Create custom templates section.
List the existing transformations.
$ vault list transform/transformations/fpe Keys ---- card-number
View the details of the newly created
card-number
transformation.$ vault read transform/transformations/fpe/card-number Key Value --- ----- allowed_roles [payments] deletion_allowed false template builtin/creditcardnumber templates [builtin/creditcardnumber] tweak_source internal type fpe
Transform secrets
The Vault client applications must have the following in their policy to perform
data encoding and decoding using the Transform secrets engine enabled at
transform/
.
# To request data encoding using any of the roles
# Specify the role name in the path to narrow down the scope
path "transform/encode/*" {
capabilities = [ "update" ]
}
# To request data decoding using any of the roles
# Specify the role name in the path to narrow down the scope
path "transform/decode/*" {
capabilities = [ "update" ]
}
Encode a value with the
payments
role.$ ENCODED_OUTPUT=$(vault write transform/encode/payments \ value=1111-2222-3333-4444 \ -format=json | jq -r '.data | .encoded_value') \ && echo encoded_value: $ENCODED_OUTPUT
Example output:
encoded_value: 9474-2096-7736-0783
Decode the value encoded with
payments
role where thevalue
is set to the returnedencoded_value
.$ vault write transform/decode/payments \ value=$ENCODED_OUTPUT
Example output:
Key Value --- ----- decoded_value 1111-2222-3333-4444
Create custom templates
Templates define the data format patterns that you wish to keep while transforming the secrets. In this section, you are going to create a transformation template which encodes British passport numbers.
![British Passport](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Ftransform%252Fvault-transform-1.png%26width%3D1172%26height%3D457&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
The passport number on a British passport is a pattern consisting of a 9-digit numeric value which can be expressed
using regular expression as (\d{9})
. The parentheses tell Vault to encode all values
grouped within; therefore, (\d{9})
will encode the entire passport number.
If you want to encode the last 7 digits leaving the first two numbers
unchanged, the expression should be \d{2}(\d{7})
.
![Scenario](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Ftransform%252Fvault-transform-4.png%26width%3D1508%26height%3D423&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
Display all the exiting templates.
$ vault list transform/template Keys ---- builtin/creditcardnumber builtin/socialsecuritynumber
Create a template named
uk-passport-tmpl
.$ vault write transform/template/uk-passport-tmpl \ type=regex \ pattern="(\d{9})" \ alphabet=builtin/numeric
Example output:
Success! Data written to: transform/template/uk-passport-tmpl
This template uses the built-in alphabet,
builtin/numeric
.Create a transformation named
uk-passport
with theuk-passport-tmpl
template.$ vault write transform/transformations/fpe/uk-passport \ template=uk-passport-tmpl \ tweak_source=internal \ allowed_roles='*'
Example output:
Success! Data written to: transform/transformations/fpe/uk-passport
Update the
payments
role to include theuk-passport
transformation.$ vault write transform/role/payments transformations=card-number,uk-passport Success! Data written to: transform/role/payments
The payments role has two transformations. Future requests to encode/decode require that the specific transformation is provided.
Encode a value with the
payments
role with theuk-passport
.$ ENCODED_OUTPUT=$(vault write transform/encode/payments value="123456789" \ transformation=uk-passport -format=json | jq -r '.data | .encoded_value') \ && echo encoded_value: $ENCODED_OUTPUT
Example output:
encoded_value: 128151714
Note
Remember that you must specify which transformation to use when you send an encode request since the payments role has two transformations associated with it.
Advanced handling
This section walks you through the new template features introduced in Vault Enterprise v1.9:
Encoding customization
In this section, you are going to create a transformation template which encodes Social Security numbers that may have an optional SSN: or ssn: prefix, and which are optionally separated by dashes or spaces.
A United States Social Security number is a 9-digit number, commonly written
using a 3-2-4 digit pattern which can be expressed using the regular expression
(\d{3})[- ]?(\d{2})[- ]?(\d{4})
. The optional prefix can be expressed using
the regular expression (?:SSN[: ]?|ssn[: ]?)?
. The use of non-capturing groups
tells Vault not to encode the prefix if it is present.
You will use the new encode_format
field to specify what the encoded output
should look like. In the value for encode_format
, variables representing the
capture groups of pattern
are used to lay out the result. The variables are in
the form of $1
, $2
, etc…, one for each of the capture groups in pattern; and
in the form of ${name}
or $name
for the named capture groups. For more
detailed information see the Go regexp.Expand
documentation.
When a template has a value for encode_format
, it will always be used. Make
sure that the resulting encoded output can be matched by pattern
, otherwise
decoding will not be possible.
Create a template named
us-ssn-tmpl
.$ vault write transform/template/us-ssn-tmpl \ type=regex \ pattern='(?:SSN[: ]?|ssn[: ]?)?(\d{3})[- ]?(\d{2})[- ]?(\d{4})' \ encode_format='$1-$2-$3' \ alphabet=builtin/numeric
Example output:
Success! Data written to: transform/template/us-ssn-tmpl
Create a transformation named
us-ssn
with theus-ssn-tmpl
template.$ vault write transform/transformations/fpe/us-ssn \ template=us-ssn-tmpl \ tweak_source=internal \ allowed_roles='*'
Example output:
Success! Data written to: transform/transformations/fpe/us-ssn
Update the
payments
role to include theus-ssn
transformation.$ vault write transform/role/payments transformations=card-number,uk-passport,us-ssn Success! Data written to: transform/role/payments
Validation
Encode values with the
payments
role with theus-ssn
transformation.$ ENCODED_OUTPUT=$(vault write transform/encode/payments value="123-45-6789" \ transformation=us-ssn \ -format=json | jq -r '.data | .encoded_value') \ && echo encoded_value: $ENCODED_OUTPUT
Example output:
encoded_value: 489-82-2140
Try encoding value that starts with
SSN
.$ vault write transform/encode/payments value="SSN:$ENCODED_OUTPUT" \ transformation=us-ssn
Example output:
Key Value --- ----- encoded_value 766-52-7759
Try encoding value that starts with
ssn
.$ vault write transform/encode/payments value="ssn:$ENCODED_OUTPUT" \ transformation=us-ssn
Example output:
Key Value --- ----- encoded_value 766-52-7759
Decode the value encoded with the
payments
role with theus-ssn
transformation where thevalue
is set to theencoded_value
.$ vault write transform/decode/payments value="$ENCODED_OUTPUT" \ transformation=us-ssn
Example output:
Key Value --- ----- decoded_value 123-45-6789
Decoding customization
When running Vault Enterprise v1.9 or later, you can specify one or more
optional formats to use during decoding. In this section, you are going to
modify the template us-ssn-tmpl
, created in the previous section, to add two
decoding formats: one to decode values separated by spaces, and the second to
decode only the last four digits.
Like encode_format
, decode_formats
have variables representing the capture
groups of pattern
to lay out the decoded output. Specifying a decode format is
optional, and it is specified as part of the path when performing the decode
operation.
Create a template named
us-ssn-tmpl
.$ vault write transform/template/us-ssn-tmpl \ type=regex \ pattern='(?:SSN[: ]?|ssn[: ]?)?(\d{3})[- ]?(\d{2})[- ]?(\d{4})' \ encode_format='$1-$2-$3' \ decode_formats=space-separated='$1 $2 $3' \ decode_formats=last-four='*** ** $3' \ alphabet=builtin/numeric
Example output:
Success! Data written to: transform/template/us-ssn-tmpl
Create a transformation named
us-ssn
with theus-ssn-tmpl
template.$ vault write transform/transformations/fpe/us-ssn \ template=us-ssn-tmpl \ tweak_source=internal \ allowed_roles='*'
Example output:
Success! Data written to: transform/transformations/fpe/us-ssn
Update the
payments
role to include theus-ssn
transformation.$ vault write transform/role/payments transformations=card-number,uk-passport,us-ssn Success! Data written to: transform/role/payments
Encode values with the
payments
role with theus-ssn
transformation.$ ENCODED_OUTPUT=$(vault write transform/encode/payments value="123-45-6789" \ transformation=us-ssn \ -format=json | jq -r '.data | .encoded_value') \ && echo encoded_value: $ENCODED_OUTPUT
Example output:
encoded_value: 489-82-2140
Decode values with the
payments
role, theus-ssn
transformation and thespace-separated
decoding format.$ vault write transform/decode/payments/space-separated value="$ENCODED_OUTPUT" \ transformation=us-ssn
Example output:
Key Value --- ----- decoded_value 123 45 6789
Decode values with the
payments
role, theus-ssn
transformation and thelast-four
decoding format.$ vault write transform/decode/payments/last-four value="$ENCODED_OUTPUT" \ transformation=us-ssn
Example output:
Key Value --- ----- decoded_value *** ** 6789
Access control
As the decode format is part of the path of the write operation during decoding, Vault policies can be used to control access to them.
# Allow decoding using any of the decode formats
path "transform/decode/us-ssn/*"
{
capabilities = ["update"]
}
# Allow decoding using only the last-four decode format
path "transform/decode/us-ssn/last-four"
{
capabilities = ["update"]
}
# Allow decoding without specifying a decode format
path "transform/decode/us-ssn"
{
capabilities = ["update"]
}
To demonstrate the functionality, create a token with a policy that will only
permit decoding using the last-four
decode format.
Define the policy in the file named
last-four.hcl
.$ tee last-four.hcl <<EOF # Allow decoding using only the last-four decode format path "transform/decode/payments/last-four" { capabilities = ["update"] } EOF
Create the
last-four
with the policy defined inlast-four.hcl
.$ vault policy write last-four last-four.hcl Success! Uploaded policy: us-ssn-last-four
Create a token with the
last-four
policy attached and store the token in the variable$LAST_FOUR_TOKEN
.$ LAST_FOUR_TOKEN=$(vault token create -format=json -policy="last-four" | jq -r ".auth.client_token")
Using the token, decode values with the
payments
role, theus-ssn
transformation and thelast-four
decoding format.$ VAULT_TOKEN=$LAST_FOUR_TOKEN vault write transform/decode/payments/last-four \ value="766-52-7759" \ transformation=us-ssn
Example output:
Key Value --- ----- decoded_value *** ** 6789
Trying to use the token to decode with the
payments
role with theus-ssn
transformation with thespace-separated
decode format, or without specifying a decode format will fail.$ VAULT_TOKEN=$LAST_FOUR_TOKEN vault write transform/decode/payments \ value="766-52-7759" \ transformation=us-ssn
Example output:
Error writing data to transform/decode/payments: Error making API request. URL: PUT http://localhost:8200/v1/transform/decode/payments Code: 403. Errors: * 1 error occurred: * permission denied
Create custom alphabets
Alphabet defines a set of characters (UTF-8) that is used for FPE to determine the validity of plaintext and ciphertext values.
These are a number of built-in alphabets available to use.
Alphabets | Description |
---|---|
builtin/numeric | Numbers |
builtin/alphalower | Lower-case letters |
builtin/alphaupper | Upper-case letters |
builtin/alphanumericlower | Numbers and lower-case letters |
builtin/alphanumericupper | Numbers and upper-case letters |
builtin/alphanumeric | Numbers and letters |
New alphabets can be created to satisfy the template requirements.
To learn the command, create a non-zero-numeric
alphabet which contains
non-zero numbers.
Display existing alphabets.
$ vault list transform/alphabet
Keys
----
builtin/alphalower
builtin/alphanumeric
builtin/alphanumericlower
builtin/alphanumericupper
builtin/alphaupper
builtin/numeric
Create an alphabet named non-zero-numeric
.
$ vault write transform/alphabet/non-zero-numeric alphabet="123456789"
Success! Data written to: transform/alphabet/non-zero-numeric
This new alphabet consists of only characters from the provided set 123456789
.
Data masking
Data masking is used to hide sensitive data from those who do not have a clearance to view them. For example, this allows a contractor to test the database environment without having access to the actual sensitive customer information. Data masking has become increasingly important with the enforcement of General Data Protection Regulation (GDPR) introduced in 2018.
The following steps demonstrate the use of masking to obscure your customer's phone number since it is personally identifiable information (PII).
Note
Masking is a unidirectional operation; therefore, encode
is the
only supported operation.
You will create a phone-number-tmpl template which masks phone numbers with its country code visible.
![Scenario](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Ftransform%252Fvault-transform-5.png%26width%3D1599%26height%3D612&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
Create a template named "phone-number-tmpl" with country code.
$ vault write transform/template/phone-number-tmpl type=regex \ pattern="\+\d{1,2} (\d{3})-(\d{3})-(\d{4})" \ alphabet=builtin/numeric
Example output:
Success! Data written to: transform/template/phone-number-tmpl
Create a transformation named "phone-number" with the
phone-number-tmpl
template and allow all roles to use it.$ vault write transform/transformations/masking/phone-number \ template=phone-number-tmpl \ masking_character=# \ allowed_roles='*'
Example output:
Success! Data written to: transform/transformations/masking/phone-number
The
type
is set tomasking
and specifies themasking_character
value instead oftweak_source
. The default masking character is*
if you don't specify one.Test and verify the newly created
phone-number
mask transformation by adding thephone-number
transformation to thepayments
role.$ vault write transform/role/payments \ transformations=card-number,uk-passport,phone-number
Example output:
Success! Data written to: transform/role/payments
Encode a value with the
payments
role with thephone-number
transformation.$ vault write transform/encode/payments value="+1 123-345-5678" \ transformation=phone-number
Example output:
Key Value --- ----- encoded_value +1 ###-###-####
Batch input processing
When you need to encode more than one secret value, you can send multiple secrets in a request payload as batch_input instead of invoking the API endpoint multiple times to encode secrets individually.
Example Scenario 1:
You received a credit card number, British passport number and a phone number
of a customer and wish to transform all these secrets using the payments
role.
Create an API request payload with multiple values, each with the desired transformation.
$ tee input-multiple.json <<EOF { "batch_input": [ { "value": "1111-1111-1111-1111", "transformation": "card-number" }, { "value": "123456789", "transformation": "uk-passport" }, { "value": "+1 123-345-5678", "transformation": "phone-number" } ] } EOF
Encode all the values with the
payments
role.$ curl --header "X-Vault-Token: $VAULT_TOKEN" \ --request POST \ --data @input-multiple.json \ $VAULT_ADDR/v1/transform/encode/payments | jq ".data"
Example output:
{ "batch_results": [ { "encoded_value": "7998-7227-5261-3751" }, { "encoded_value": "908547441" }, { "encoded_value": "## ###-###-####" } ] }
Example Scenario 2:
An on-premises database stores corporate card numbers and your organization decided to migrate the data to another database. You wish to encode those card numbers before storing them in the new database.
Create a request payload with multiple card numbers.
$ tee payload-batch.json <<EOF { "batch_input": [ { "value": "1111-1111-1111-1111", "transformation": "card-number" }, { "value": "2222-2222-2222-2222", "transformation": "card-number" }, { "value": "3333-3333-3333-3333", "transformation": "card-number" }, { "value": "4444-4444-4444-4444", "transformation": "card-number" } ] } EOF
Encode all the values with the
payments
role.$ BATCH_ENCODED=$(curl --header "X-Vault-Token: $VAULT_TOKEN" \ --request POST \ --data @payload-batch.json \ $VAULT_ADDR/v1/transform/encode/payments | jq ".data") \ && echo $BATCH_ENCODED
Example output:
{ "batch_results": [ { "encoded_value": "7998-7227-5261-3751" }, { "encoded_value": "2026-7948-2166-0380" }, { "encoded_value": "3979-1805-7116-8137" }, { "encoded_value": "0196-8166-5765-0438" } ] }
Decode the values
Create a request payload with the encoded card numbers.
$ tee payload-batch.json <<EOF { "batch_input": [ { "value": "$(echo $BATCH_ENCODED | jq -r '.batch_results[0].encoded_value')", "transformation": "card-number" }, { "value": "$(echo $BATCH_ENCODED | jq -r '.batch_results[1].encoded_value')", "transformation": "card-number" }, { "value": "$(echo $BATCH_ENCODED | jq -r '.batch_results[2].encoded_value')", "transformation": "card-number" }, { "value": "$(echo $BATCH_ENCODED | jq -r '.batch_results[3].encoded_value')", "transformation": "card-number" } ] } EOF
Decode all the values with the
payments
role.$ curl --header "X-Vault-Token: $VAULT_TOKEN" \ --request POST \ --data @payload-batch.json \ $VAULT_ADDR/v1/transform/decode/payments | jq ".data"
Example output:
{ "batch_results": [ { "decoded_value": "1111-1111-1111-1111" }, { "decoded_value": "2222-2222-2222-2222" }, { "decoded_value": "3333-3333-3333-3333" }, { "decoded_value": "4444-4444-4444-4444" } ] }
Additional discussion
Bring your own key (BYOK)
Vault Enterprise users running version 1.12.0 or greater can use the BYOK functionality to import an existing encryption key generated outside of Vault, and use it with Transform secrets engine.
The target key for import can originate from an HSM or other external source, and must be prepared according to its origin before you can import it.
The example shown here will use a 256-bit AES key, referred to as the target key. To successfully import the target key, you must perform the following operations to prepare it.
Generate an ephemeral 256-bit AES key.
Wrap the target key using the ephemeral AES key with AES-KWP.
Wrap the AES key under the Vault wrapping key using RSAES-OAEP with MGF1 and either SHA-1, SHA-224, SHA-256, SHA-384, or SHA-512.
Delete the ephemeral AES key.
Append the wrapped target key to the wrapped AES key.
Base64 encode the result.
A specific code example for preparing and wrapping the key for import is beyond the scope of this tutorial. For more details about wrapping the key for import including instructions for wrapping key from an HSM, refer to the key wrapping guide.
Before you can wrap the key for import, you must read the wrapping key from Vault so that it can be used to prepare your key.
$ vault read -field=public_key transform/wrapping_key
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0aBVXa1Z8X1n1z3smBjU
J+Vm16AznaIrDxWrMAEsQt8xqh33gNMdzsdhNGy0+UzF2mxs9lUbp3uXJxqSaJDB
B+yeSqB4svgcQneg5pZnpZ1Af1s8X6uHTjZZxlHpJ0wumA//zWwg+0TtDij16C5o
Qpep7+UqA9qEcVheCP4puyQXssGH6vwbV4nspNTd1E5RgXe9xpdUKW+ih0pJWbNN
wiW1L6YP8F6dQ4adr6Nm/UUoTpKnvdippT980SLFrkl7v6xh+UKC51bS5fxtQ4Xl
dFCZYWO+Z7HCu3dWEte0Q5vhvbDLCJYKQ/avsQ8EXJtBYzKS7rtvDOYZLOFAgGs4
yeUgVOaIEHXmp+r7CdXnMlo9/5qN02i16HavTgQvaBbPclgH2NWE2JP4sKNsFfyF
DIL9btnUjjgTl6/KaxlVBmZyx96A0V1UTEphziFgeUVw2Rc7PkyOX9lXdAu34A3i
0kgjZC1Y8x/TnGWJ4ofyFBM8RmSaoe4e9C2i2VjHFzJX3ya53Rewy/e+MdvG3tlI
tIEXoanXtTAPbpBJw9tY4FLO8WFsQuBiYg/ovO3ZcYzK5tZwdPnWLjgiXitFchUs
s0lEYFARLap6i/nXxmJnE8jJeAoZ6QSOpNf9ss4rIM9liFvHy+O5Fb3qRLg4zu9M
ERqkZVYfyxp4bChyZwOJD+ECAwEAAQ==
-----END PUBLIC KEY-----
The output is the (4096-bit RSA) wrapping key.
Use the wrapping key value at step 3 in the previously detailed preparation steps. Once you have prepared and base64 encoded the ciphertext, export the value to the environment variable IMPORT_CIPHERTEXT
.
Example:
$ export IMPORT_CIPHERTEXT=XQiDLXkoN2nE3UXIUnNb4joUSUDyB0OsD/U0T3WpSGuatB2Rd89yj57v1eDb2RDq8tt904iCftDY1c4fJXBlNhvZtRgS45zV+hZZA806YZ2a2ha3rFFEEobRLxs0aCoyq+iqBsOxG4pHG5irtzqfPz5IJRtN0SXxcoOodRm0hGU69++iZO5GkJ8k6Y4513Y/Qwnm/4rBToE9E6toQSTf7exF3yp0wxe/GSfFKCr2aVvYnyK0j+UYNbDeK0M7JOSQ38JmC62oPEfYdI+onyzQxQJsXec/5GOZWsuNnhZtvDNeslnAoC20HiMxl2aMQn4PAvWO+HtlhleXKjaWgDuopgj+qjsSny72pKo2hSntGsamaPqVZQHDmYpIQP4aBsqQeq5bXtDc407cAEPl+MA5huzazdNgMeZqCYZNYFPlUVtZ2dJ6A9ogFE7B8RLAEf4t717kTvlQ0qsXwXOGtoFuw/AQGMTF1CNmCVEJWZBPkxH6GXT6N3QAgwRMYVP6r//IAZ4FKmZ8V4nwmp2uBN0H49XekH1yULa3lWGSXx3CGGy3hYuUHGi6B7mWEH7B0jQgk3EtU1JlgtpTk/jlnL7mpa8VRZefAZqFDX6DoEjioXtqbQAA1Vvkke/PDN3OL6a5aj9kr2UJvlRPfwYsSHw+l8oyazrLs8aKHiaVJAfsVD5y02ipMIGFwaUjswYeW1LxtXg4FVfINzUAoq7z9mvQDby1mvh0Mtr9
Create a new transformation role named physical-access
to use for the proximity-card
transformation that you will import the key into.
$ vault write transform/role/physical-access transformations=proximity-card
Success! Data written to: transit/role/physical-access
Create a new template named identifier
to match a proximity card identifier string having the format shown in this example: 8A642EC3-3C8A-40C2-8AC0-A039ECC0FFEE
.
$ vault write transform/template/identifier \
type=regex \
pattern="(\b[A-Z\d]{8}\b)[-/](\b[A-Z\d]{4}\b)[-/](\b[A-Z\d]{4}\b)[-/](\b[A-Z\d]{4}\b)[-/](\b[A-Z\d]{12}\b)" \
alphabet=builtin/alphanumericupper
Example output:
Success! Data written to: transit/template/identifier
Import the key into the proximity-card
transformation; add the allowed_roles parameter and specify the physical-access
role.
$ vault write transform/transformations/fpe/proximity-card/import \
ciphertext=$IMPORT_CIPHERTEXT \
allowed_roles=physical-access \
template=identifier \
tweak_source=internal
Example output:
Success! Data written to: transform/transformations/fpe/proximity-card/import
Try using the newly imported key to encode a value.
$ vault write transform/encode/physical-access value="19742EC3-3C8A-B0C2-23C0-A039ECA9DD23" \
transformation=proximity-card
Key Value
--- -----
encoded_value OGCIWU8A-RBW0-R7FJ-8IZZ-VZZRIWZQ3P5Z
The imported key is working, and the encoded value returned by the proximity-card
transformation is using the imported key.
Note
The FPE transformation does not currently support versioning or rotating of its encryption keys.
Next steps
To actually integrate your application with Vault and leverage the transform secrets engine, there are a number of resources which must be configured.
Before the application can even request data transformation, it first needs to authenticate with Vault. Therefore, an auth method (e.g. AWS, Kubernetes, AppRole) must be enabled and configured for the application to use. In addition, an appropriate policy must be created and attached to the client token.
![](/_next/image?url=https%3A%2F%2Fcontent.hashicorp.com%2Fapi%2Fassets%3Fproduct%3Dtutorials%26version%3Dmain%26asset%3Dpublic%252Fimg%252Fvault%252Ftransform%252Fvault-transform-6.png%26width%3D1521%26height%3D562&w=3840&q=75&dpl=dpl_Cfraazb7cTY3kxdHoe7jRFiYgXDj)
You can codify the Vault configuration using Terraform, and make the configuration repeatable. The Terraform Vault Provider supports the transform secrets engine. It can create policies, enable and configure auth methods and more.
Refer to the Codify Management of Vault Enterprise Using Terraform tutorial to learn how to leverage Terraform.
On the application side, you can run Vault Agent to authenticate with Vault and manage the lifecycle of the client token. Refer to the following tutorials to learn more about Vault Agent:
The Encrypting Data while Preserving Formatting with the Vault Enterprise Transform Secrets Engine blog post introduces some code examples to invoke the transform secrets engine using Vault API.
Clean up
Unset the
VAULT_TOKEN
environment variable.$ unset VAULT_TOKEN
Unset the
VAULT_ADDR
environment variable.$ unset VAULT_ADDR
Unset the
IMPORT_CIPHERTEXT
environment variable.$ unset IMPORT_CIPHERTEXT
Remove the payload files.
$ rm -f input.json payload-batch.json payload.json input-multiple.json last-four.json payload-template.json
You can stop the Vault dev server by pressing Ctrl+C where the server is running. Or, execute the following command.
$ pgrep -f vault | xargs kill
If you are using Vault Dedicated, you can delete the cluster from the HCP Portal.
Summary
The Transform secrets engine performs secure data transformation and tokenization against the input data. Transformation methods may encompass NIST vetted cryptographic standards such as format-preserving encryption (FPE) via FF3-1, but can also be pseudonymous transformations of the data through other means, such as masking. This tutorial walked through the use of the Transform secrets engine step-by-step.
Limits:
The Transform secrets engine obeys the FF3-1 minimum and maximum sizes on the length of an input, which are a function of the alphabet size.
Help and reference
- Transform Secrets Engine (API)
- Transform Secrets Engine
- Encrypting Data while Preserving Formatting with the Vault Enterprise Transform Secrets Engine
Next steps
The Encrypting Data with Transform Secrets Engine tutorial introduces demo applications written in Go and Java to learn how to implement the Transform secrets engine API in your application.
If you are interested in data tokenization, refer to the Tokenize Data with Transform Secrets Engine tutorial.