This is a Go program to simplify the encryption & decryption of byte arrays, using 256 bit AES keys in Galois/Counter Mode (GCM), with cloud-based KMS services (currently only AWS and GCP) and multiple key layers (specifically Envelope Encryption).
This avoids the need to send secret data to Cloud KMS services when encrypting (only your own data encryption key is sent), and allows for encryption of data bigger than 4096 bytes (a restriction often imposed by Cloud KMS).
Darwin, Linux and Windows Binaries can be downloaded from the Releases page.
Try it out:
$ mantle -h
$ git clone git@github.com:ovotech/mantle.git
$ cd mantle
$ go build
$ ./mantle -h
$ go get -u github.com/ovotech/mantle
Create a symmetric Customer Managed Key (CMK). See AWS doco here.
It's recommended to give the key an alias, allowing you to change the underlying CMK if required. Your user will permission to use the CMK.
Authenticate your AWS CLI, see here for help.
Now give it a go:
$ echo "helloworld" > plain.txt
$ ./mantle encrypt -n alias/my-kms-key -m aws
$ cat cipher.txt
$ ./mantle decrypt -m aws
$ cat plain.txt
-
mantle
doesn't need the keyId (passed in using the-n,--keyName
flag) when decrypting as it's stored in the ciphertext. -
You can use the Key ID, Key ARN, Alias name or Alias ARN in the
-n,--keyName
flag when encrypyting, as described here. -
plain.txt
andcipher.txt
are used as default filepaths for encrypt and decrypt respectively. You can control this in each operation using the-f,--filepath
flag.
You'll need to create a Key Ring and Key in Google KMS to allow the tool to encrypt/decrypt.
If you have gcloud
set up locally, and your user has the Cloud KMS CryptoKey Encrypter
and/or Cloud KMS CryptoKey Decrypter
Role(s), the tool will
already be able to encrypt/decrypt.
If you're running the binary in an automated way (i.e. with a Service Account):
- The Service Account will need the Cloud KMS CryptoKey Encrypter and/or Decrypter Role(s)
- Drop the Service Account's key.json onto the host you're running the tool on
- Set the
GOOGLE_APPLICATION_CREDENTIALS
env var as the path of the key.json
Using gcloud
, you can do this by issuing:
# get the name of the Keyring that 'holds' the required Key
$ gcloud kms keyrings list --location <location>
# get the name of the Key
$ gcloud kms keys list --location <location> --keyring <keyring_name>
The NAME
value returned by the last command is Google's Resource ID
for the
Key.
It's this value that you can give to the mantle
binary in the
-n,--keyName
flag, to get it to work.
Alternatively to using gcloud
you can get the Resource ID from the Google Cloud Console; click on the KeyRing
you want to use, and select "Copy Resource ID" from the menu to the right of the
correct Key.
To test this out, you should be able to:
$ export KEY_NAME="projects/<project_name>/locations/<location>/keyRings/<keyring_name>/cryptoKeys/<key_name>"
$ echo "helloworld" > plain.txt
$ mantle encrypt -n $KEY_NAME -m gcp
$ cat cipher.txt
$ mantle decrypt -n $KEY_NAME -m gcp
$ cat plain.txt
- The
-m,--kmsProvider
flag defaults togcp
. This is to makemantle
backwards compatible for GCP users who don't use that flag (whenmantle
was first released, GCP was the only provider integrated).
-
A new 256-bit AES key and a 96-bit nonce are generated every time you issue the
encrypt
command. The key and nonce are used to encrypt your plaintext. -
The AES key, also known as the Data Encryption Key (DEK), is then encrypted using the Cloud KMS Service.
-
The concatenated encrypted data, nonce and encrypted DEK are then given back to the user as the ciphertext.
Decrypting is the same process but in reverse.
For AWS, the structure of the ciphertext is:
encryptedData[n]nonce[12]encryptedDEK[185]
For GCP, the structure of the ciphertext is:
encryptedData[n]nonce[12]encryptedDEK[114]
The length of the encryptedDEK
is determined by the length of the response
from the KMS providers. For AWS this is 185 chars, for GCP it's 114 chars.
The length of the encrypted data will depend on the length of your plaintext.
When encrypting, newline chars are by default inserted into the ciphertext,
every 40 chars. This is to play nicer with any max line lengths when storing in
source code. This functionality can be disabled using the -s, --singleLine
flag.
So long as the decrypting process is only removing newline chars at the end of lines, it shouldn't need to differentiate the two 'modes'
Mantle uses the crypto/rand Reader to
generate a new IV every time the encrypt
command is called.
"Reader is a global, shared instance of a cryptographically secure random number generator.
On Linux, Reader uses getrandom(2) if available, /dev/urandom otherwise. On OpenBSD, Reader uses getentropy(2). On other Unix-like systems, Reader reads from /dev/urandom. On Windows systems, Reader uses the CryptGenRandom API. On Wasm, Reader uses the Web Crypto API."
The IV is created by reading 96 bits (12 bytes) from this Reader.
By default, when performing either encrypt
or decrypt
commands, the tool
will zero-fill and delete the source file, so plain.txt or cipher.txt (or an
overriding filepath you've set) respectively.
When decrypting, you can use the -r,--retainCipherText
flag in order to
retain the ciphertext file. There's no option to retain the source file when
encrypting.
$ mantle encrypt -n <key_name>
Encrypting...
-----BEGIN (ENCRYPTED DATA + DEK) STRING-----
y+PvJrf0QJKKSp85C0MN6q2v7EhMeorNJG+5FLiN
wV/Wow6eWHFL80x3xl7vIgDVN5CdRAOVpZL2kJV3
coDbctszL5LJHaLL22YVYaJwojETz5Aff4Kss98p
MIRahCJ1D8EFNoBbTAQTUGNJAJGc11YcX3sWpsYB
h3BookBa6KEvnmNFfw8F6M71zpdmByS1p/k8/1Z/
TAX/Dj0wxcm2g/ez7gA0e/vFQXQjJYqSkb0xJuQX
SVaDoXap3HF7NbikcklBPBkDvy408Hogapvh4OF2
vL9tlhGoERUkrWcwXQfcZjk1B3Sjh45UDTHySTs+
m4Eco7MOur6LvfrGKJuX6qJubppxUDv2ZTCeMCrK
d9AjmCqleD/iSthZN1FKjQ3zLowlnsvWIMnaeEC+
h5W8NIjKm4YQCY2yGj3V6AhdBMvujXLX1aYbIHSf
GfIzLhHSKI7vUm0RFN5irblcoC+sBkRf8NAKJAB1
PbjZJT8wZ94zMUnqrUNNCJqzoky5PFiAY0x077co
SHATyRJJAOR2fnkCjptlffrP0/y8Jhs7ogtttzwt
mkJtdbf9ltQw2ak1OJI3h7NC9vLqfDzGQFeO396C
RRt3E3ly9MifB+cFe4Fnowcq0g==
-----END (ENCRYPTED DATA + DEK) STRING-----
Encryption successful, ciphertext available at ./cipher.txt
Wiped 340 bytes from ./plain.txt.
Your ciphertext is the string between (not including) the BEGIN
and END
markers. The resulting cipher.txt
file will only contain the ciphertext
string.
Contributions are very welcome, please fork or branch and raise a PR.