Security is hard. It's hard because there are so many things that can go wrong along the way. Attackers only need to find one small gap to make it through whereas developers and IT Pros have to think about all the possible attack permutations and potential security vulnerabilities every step of the way. That's why we talk about "security in layers" when it comes to building systems that handle sensitive data. And unless you're creating static marketing pages, it's very likely that your solution will need to work with some data.
Where do I start?
If you're running on Azure (or other cloud providers) there are tools to help you keep your data secure both at rest and in transit. This is great document that can help you get a high level overview of Azure data security and encryption. And there are many more tools to help you lock down and tightly monitor access to your data: Azure Security Center, [Azure Advisor] and more etc etc.
In this blog post we're going to look at client data encryption/decryption using .NET and Azure key Vault. However, if you're not a .NET developer, the practices and ideas in this blog post are applicable and available to every language that's supported by the Azure SDK and since everything is a wrapper around our Azure REST API, you can even roll out your own libraries (if you decide to choose so)
The main point of this blog post is to help developers implement a robust solution to encrypt/decrypt data without having to worry about the underlying cryptographic implementation. Therefore, instead of spending weeks or months learning how cryptography works (symmetric, asymmetric, recommended algorithms and hard maths), you delegate the work to a service like Azure Key Vault. This way, you have a reliable and scalable service to manage your keys and perform complex operation outside your code! And that's the key: removing sensitive operations from you code makes it more robust
Prerequisites
To be able to use the code in this solution you'll need the following:
- An Azure Subscription get a FREE one
- An Azure Key Vault (create one)
- .NET 6 Download
- VS Code Download
One thing we haven't discussed yet is that this solution requires internet access to call into Key Vault. Consequently, if you have an on prem app that needs to run in isolation and not call into Azure, you'll need to look at equivalent, on prem solutions like HashiCorp Vault etc.
Account security and best practices
Most developers when working with Azure tend to use their own accounts out of convenience. However, this is not ideal. Instead, they should use Service Principals with restricted access rights to only the necessary resources. When moving to production, the Azure.Identity library makes it extremely easy to switch to Managed Identities without changing the code. Security end-to-end.
Let's create a Service Principal with the right permissions:
Open your favorite terminal (that has the Azure CLI installed) or jump straight to Azure Cloud Shell and run the following commands:
az ad sp create-for-rbac -n "cm-keyvault-crypto" --role "Key Vault Crypto User"
az keyvault set-policy --name cm-identity-kv --object-id a4e0e9c6-c507-4449-a9c4-25243ef61fe9 --key-permissions decrypt encrypt list get
az login --service-principal -u 2ff15c46-97bd-424a-b97a-433c8e5640d7 -p <your secret> --tenant 72f988bf-86f1-41af-91ab-2d7cd011db47
The first command creates a service principal account and assigns it to Crypto user RBAC role. Although we will not be using RBAC for this example, it's recommended practice to assign the appropriate roles to your SPs. Also note that the default az ad sp create-for-rbac
command behavior will change and stop assigning Contributor role by default - a great security measure going forward! Contributor is an overprivileged role and we want to avoid using such accounts for dev/test/prod.
The second command assigns the appropriate Access Policies to Azure Key Vault to allow us retrieve keys to use for encryption/decryption.
The final command signs in the Service Principal in the Azure CLI as we will be using this to provide credentials to our code.
Let's write some code
For this solution we are going to use the .NET CLI and VS Code. In your terminal type the following:
dotnet new console -n EncryptDecryptSample
dotnet add package install Azure.Identity
dotnet add package Azure.Security.KeyVault.Keys -v 4.3.0-beta.2
Open the project in VS Code with code .
. Now we can write the necessary code to encrypt and decrypt data. Update the Program.cs
file with the following code:
using System.Text;
using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
// vault URL could be passed as a parameter
var KeyVaultUrl = "https://cm-identity-kv.vault.azure.net";
// using Azure AD to support secretless authentication to Azure Key Vault
var credentials = new ChainedTokenCredential(
new AzureCliCredential(),
new ManagedIdentityCredential()
);
var client = new KeyClient(new Uri(KeyVaultUrl), credentials);
//this could be parametarized as you may wish to pass different keys for different operations
var keyName = "MyEncryptionKey";
//get the key (or create one on the fly - very unlikely in a production environment)
KeyVaultKey key;
try
{
key = await client.GetKeyAsync(keyName);
}
catch (RequestFailedException ex) when (ex.Status == 404)
{
key = await client.CreateRsaKeyAsync(new CreateRsaKeyOptions(keyName));
}
//get the crypto client of the key
var cryptoClient = client.GetCryptographyClient(key.Name, key.Properties.Version);
//do the fun stuff
var plainText = "My secret message";
var byteData = Encoding.Unicode.GetBytes(plainText);
Console.WriteLine("Encrypting...");
var encryptedResult = await cryptoClient.EncryptAsync(EncryptionAlgorithm.RsaOaep, byteData);
Console.WriteLine($"Encrypted data: {Convert.ToBase64String(encryptedResult.Ciphertext)}");
Console.WriteLine("Decrypting...");
var decryptedResult = await cryptoClient.DecryptAsync(EncryptionAlgorithm.RsaOaep, encryptedResult.Ciphertext);
Console.WriteLine($"Decrypted data: {Encoding.Unicode.GetString(decryptedResult.Plaintext)}");
If the Service Principal is configured correctly then running the code with dotnet run
should yield the following output:
If the Key Vault permissions are wrong (they shouldn't be if you followed the instructions above and signed in with the right account), you may receive the following error:
The code makes use of the Azure.Identity library to authenticate to Azure Key Vault and then instantiate a KeyClient
and a CryptographyClient
to perform the necessary operations. This way, we never have to use and Keys or Secrets to authenticate to Key Vault making our application even more secure.
NOTE: this code attempts to retrieve a key and, if not found, it creates a new one. The current Service Principal (as configured earlier) doesn't have the appropriate permission to create keys so the code will fail. We can either create a key using a more privileged account, or temporarily update the SP with a
Create
permission. In real world scenarios, the IT Admins or Security team will be responsible for creating (and rolling) keys in Key Vault.
Where is the code?
You can grab the code from [this GitHub repo] - notice how it jumps straight to the (https://github.dev/425show/EncryptDecryptDataWithKeyVault)
Summary
Application security is hard but we shouldn't be intimated by it, especially since, as developers we are spoiled for choice these days! Many of the hard problems that we had to solve in the past are now a service or product that we can easily pull off the shelf and add it to our solution. Therefore, if you need to encrypt/decrypt data in your application, you can now do it with less than 20 lines of code using Azure Key Vault backed by Azure AD. Let me know in the comments if you have any Qs :)
Top comments (0)