Skip to content
Ed Freeman By Ed Freeman Software Engineer II
Using Azure Key Vault for Encryption in C# - A Simple Tutorial

Do you need to encrypt a piece of data in your application? Do you want Azure Key Vault to secure the key? Well, using C# along with a couple of libraries from the Azure SDK, it couldn't be easier to get up and running.

We're going to be using the v4 version of the Azure Security Key Vault Keys client library. We're also going to be needing the Azure Identity client library.

For this intro, I'm going to assume that you have an existing Azure Key Vault. If you haven't got one, here's how to create one.

I'm also going to be authenticating as myself to make the requests to Key Vault purely for demonstration purposes, but in a "real" application you'll almost certainly want to use a managed identity or a service principal. The good news is - the steps/code you'll see below won't change much at all if you opt for either of those alternatives.

For the Key Vault Key operations detailed in this blog to work, the principal under whose identity you're making the requests needs to have an access policy defined, assigned the Get and Create key management operations, and the Encrypt and Decrypt cryptographic operation*^. The easiest way to set an access policy is through the Azure Portal, by navigating to your Key Vault, selecting the "Access Policies" tab, and clicking "Add Access Policy". Then, select the above permissions, select the relevant principal, and click "Add". Alternatively, you can use the CLI or PowerShell.

* In most cases, it's quite likely that no single identity will have all 3 of these permissions. Generally, the identity creating the key will be different to that retrieving the key/using the key to perform cryptographic operations. Purely for demonstration purposes, I've assigned these permissions to the same identity (myself).

Programming C# 12 Book, by Ian Griffiths, published by O'Reilly Media, is now available to buy.

^ Also, it transpires that the Get permission seems to incorporate the Encrypt permission, so when a user has the former permission, the latter doesn't seem to have to be explicitly granted. However, as mentioned above, it could well be that you have multiple identities performing the different operations. Additionally, it's good to be explicit about the permissions you're assigning to identities, rather than relying on implicitly granted permissions.

We're going to create a simple .NET Core 3.1 console application, and taking advantage of the async main method functionality added in C# 7.1 to more realistically imitate asynchronous code that would likely appear in more fully-fledged applications.

That's it - prerequisites and preamble out the way. Let's get going.

Method

Once the .NET Core console app has been created, we should start by adding those package references as project dependencies. So, that's Azure.Identity and Azure.Security.KeyVault.Keys, like the following:

Adding Dependencies via Azure Identity and Azure Security EncryptDecryptKV

Let's add a few variables we're going to need:

string keyVaultName = "myfavouritekeyvault";
string keyVaultUri = "https://" + keyVaultName + ".vault.azure.net";
string keyVaultKeyName = "myfavouritekey";
string textToEncrypt = "StuffIDoNotWantYouToKnow";

Now we have those bits defined, we start by creating a KeyClient we'll use to interact with the keys in Key Vault:

var keyClient = new KeyClient(new Uri(keyVaultUri), new DefaultAzureCredential());

Naturally, Azure Key Vault will only give out the keys it stores to identities it trusts. From the preamble further up this blog, we should have assigned our identity with the access policies it requires to perform the operations it needs to perform. When we're interacting with Key Vault through the SDK, we need to tell it whom it's interacting with. That's what the second parameter of the KeyClient constructor does - it's a TokenCredential, which represents a credential containing a valid AAD token. The DefaultAzureCredential class derives from the TokenCredential class, and it tries to retrieve a credential through a number of means:

  1. EnvironmentCredential - if certain environment variables have been set, authentication with this credential type will be attempted. Used for client-secret flow (service principal), or username-password flow.
  2. ManagedIdentityCredential - if the deployment environment has been assigned a managed identity, this flow will be attempted.
  3. SharedTokenCacheCredential - uses tokens in the local cache that's shared between Microsoft apps. This is the method that is being used in this demo - since I'm signed into my AAD account in Visual Studio, everything just works in this scenario.
  4. InteractiveBrowserCredential - launches the default browser for interactive authentication.

Now we have our client, we can go ahead and create a key (presuming one doesn't already exist):

await client.CreateRsaKeyAsync(new CreateRsaKeyOptions(keyVaultKeyName));

Once created, let's retrieve the same key and assign it to a variable:

KeyVaultKey key = await client.GetKeyAsync(keyVaultKeyName);

Now, to perform the encryption, we actually need a different client - and that's a CryptographyClient. Typically, the steps to perform the encryption/decryption would happen in a different program to the creation of the Key, but for demonstration purposes, I've included it in the same program here. So, let's go ahead and initialize a cryptography client:

var cryptoClient = new CryptographyClient(key.Id, new DefaultAzureCredential());

To encrypt a string, we first need to encode it as a byte array. We then call EncryptAsync and specify the encryption algorithm we'd like to use. Finally, we can convert the ciphertext returned in the EncryptResult to a base 64 encoded string. Here's a method that does all that, given a string as an input:

static async Task<string> EncryptStringAsync(CryptographyClient cryptoClient, string input)
{
    byte[] inputAsByteArray = Encoding.UTF8.GetBytes(input);

    EncryptResult encryptResult =  await cryptoClient.EncryptAsync(EncryptionAlgorithm.RsaOaep, inputAsByteArray);

    return Convert.ToBase64String(encryptResult.Ciphertext);
}

The output will look something like this:

fSdEOFhBcNImnhnPWSrRBnmFPuWdbEW16dRzMnkNEOW2L/Y3XVzS4EqxLKEE4UhG3rGukZZ0pq0ea/1TVs9dXUtmpuhbNaqraj1oZyU5usN6/FtDGyhmBl609ciAZOpCEPulb3qFyGRicZ8lcMn3sNVWd0YmxtC0FfoWosFW0xCx+5hGLDP3MVVe7BOe2028Wp68B+Zfppj8v8GWQgLB50j3KVeaMOtCGhPSodHWojDJOoHSn53ys/iTxg6SItJPLrM9A6siJjIhvL4idUPZ440vuG0chLzYHGYy0DwCJa2n3K70DsaByN7QRKnLMJtZSvcBbndRLKujZQGQAvm3gA==

To decrypt an encrypted string, we need to convert back to a byte array and call DecryptAsync, and convert the resulting byte array back to a string again, just like the following method:

static async Task<string> DecryptStringAsync(CryptographyClient cryptoClient, string input)
{
    byte[] inputAsByteArray = Convert.FromBase64String(input);

    DecryptResult decryptResult = await cryptoClient.DecryptAsync(EncryptionAlgorithm.RsaOaep, inputAsByteArray);

    return Encoding.Default.GetString(decryptResult.Plaintext);
}

The result should be exactly what we started with.

Troubleshooting

Probably the most likely error you'll run into whilst performing any of the above operations is one related to permissions. You might receive an error like this:

Azure.RequestFailedException: Service request failed.
Status: 403 (Forbidden)
Content:
{
    "error": {
        "code": "Forbidden",
        "message": "The user, group or application 'appid=...;oid=...;numgroups=...;iss=https://sts.windows.net/.../' does not have keys create permission on key vault '...;location=...'. For help resolving this issue, please see https://go.microsoft.com/fwlink/?linkid=2125287",
        "innererror": {
            "code": "AccessDenied"
        }
    }
}

Remember that we need four permissions for this code:

  • keys create
  • keys get
  • keys encrypt
  • keys decrypt

which can be set by adding an access policy, as explained further up this blog. And just to reiterate my earlier point - generally these three permissions won't be assigned to the same identity - rather, separate identities performing the separate operations would get the permissions they need - and only the permissions they need.

Conclusion

Anyway, that's it - a round-trip of encryption and decryption of a string using a key managed by Azure Key Vault.

Azure Weekly is a summary of the week's top Microsoft Azure news from AI to Availability Zones. Keep on top of all the latest Azure developments!

Here's the full code:

using System;
using System.Text;
using System.Threading.Tasks;
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;

namespace EncryptDecryptKV
{
    class Program
    {
        static async Task Main()
        {
            string keyVaultName = "myfavouritekeyvault";
            string keyVaultUri = "https://" + keyVaultName + ".vault.azure.net";
            string keyVaultKeyName = "myfavouritekey";
            string textToEncrypt = "StuffIDoNotWantYouToKnow";

            var client = new KeyClient(new Uri(keyVaultUri), new DefaultAzureCredential());

            await client.CreateRsaKeyAsync(new CreateRsaKeyOptions(keyVaultKeyName)).ConfigureAwait(false);

            KeyVaultKey key = await client.GetKeyAsync(keyVaultKeyName).ConfigureAwait(false);

            var cryptoClient = new CryptographyClient(key.Id, new DefaultAzureCredential());

            string encryptedString = await EncryptStringAsync(cryptoClient, textToEncrypt).ConfigureAwait(false);

            Console.WriteLine($"Encrypted string: {encryptedString}");

            string decryptedString = await DecryptStringAsync(cryptoClient, encryptedString).ConfigureAwait(false);

            Console.WriteLine($"Decrypted string: {decryptedString}");
        }

        static async Task<string> EncryptStringAsync(CryptographyClient cryptoClient, string input)
        {
            byte[] inputAsByteArray = Encoding.UTF8.GetBytes(input);

            EncryptResult encryptResult =  await cryptoClient.EncryptAsync(EncryptionAlgorithm.RsaOaep, inputAsByteArray).ConfigureAwait(false);

            return Convert.ToBase64String(encryptResult.Ciphertext);
        }

        static async Task<string> DecryptStringAsync(CryptographyClient cryptoClient, string input)
        {
            byte[] inputAsByteArray = Convert.FromBase64String(input);

            DecryptResult decryptResult = await cryptoClient.DecryptAsync(EncryptionAlgorithm.RsaOaep, inputAsByteArray).ConfigureAwait(false);

            return Encoding.Default.GetString(decryptResult.Plaintext);
        }
    }
}

Hope you've found this useful!

Ed Freeman

Software Engineer II

Ed Freeman

Ed is a Data Engineer helping to deliver projects for clients of all shapes and sizes, providing best of breed technology solutions to industry specific challenges. He focusses primarily on cloud technologies, data analytics and business intelligence, though his Mathematical background has also led to a distinct interest in Data Science, Artificial Intelligence, and other related fields.

He also curates a weekly newsletter, Power BI Weekly, where you can receive all the latest Power BI news, for free.

Ed won the Cloud Apprentice of the Year at the Computing Rising Star Awards 2019.