You can use the technique that Azure blob storage uses to encrypts data at rest (envelope method):
https://learn.microsoft.com/en-us/azure/storage/storage-client-side-encryption
KeyVault has the ability to Wrap / Unwrap (encrypt/decrypt) symmetric keys so they are safe for you to store along with your encrypted data.
Here are the general steps:
- Generaete an AES key (256 bit, CBC mode) using RNGCryptoServiceProvider
- Encrypt the data (credentials)
- Save the Initialization Vector (IV). You can just concat it to the ciphertext byte array for retrieval later when you want to decrypt - IV doesn't need protected.
- Wrap (encrypt) the genereated AES Symmetric Key using the Key in KeyVault.
- Store Wraped AES Key, IV, CipherText, and Key Version (GUID at the end of the URI in KeyVault).
- Make sure you grant the Wrap / Unwrap permissions in KeyVault to the Application Registration created in Azure AD. Use the Client Id / Application ID + Key or pfx to auth to Azure in GetToken().
You'll need these nuget packages:
Install-Package Microsoft.Azure.KeyVault
Install-Package Microsoft.Azure.KeyVault.Extensions
Install-Package Microsoft.IdentityModel.Clients.ActiveDirectory -Version 2.16.204221202
Get a ref to KeyVaultKeyResolver
KeyVaultKeyResolver cloudResolver = new KeyVaultKeyResolver(Utils.GetToken);
// Example GetToken implementation
public class Utils {
// Retrive JWT token to be used for KeyVault access.
internal async static Task<string> GetToken(string authority, string resource, string scope)
{
var authContext = new AuthenticationContext(authority);
// Could use pfx instead
ClientCredential clientCred = new ClientCredential(
ConfigurationManager.AppSettings["clientId"],
ConfigurationManager.AppSettings["clientSecret"]);
AuthenticationResult result = await authContext.AcquireTokenAsync(resource, clientCred);
if (result == null)
throw new InvalidOperationException("Failed to obtain the JWT token.");
return result.AccessToken;
}
}
Once you have have KeyResolver, you can get an IKey to Wrap / Unwrap your AES symmetric key as follows...
Wrap / Encrypt AES Key
The keyID is the URI from Key Vault and aesKey is the byte[] of your AES key to encrypt:
// Resolve an IKey by Key ID from URI in KeyVault
var keyEncryptionKey = cloudResolver.ResolveKeyAsync(keyId, CancellationToken.None).GetAwaiter().GetResult();
// Take our gen'ed AES Key and wrap (encrypt) it.
Tuple<byte[], string> wrappedKey = keyEncryptionKey.WrapKeyAsync(aeskey, null /* algorithm */, CancellationToken.None).GetAwaiter().GetResult();
The byte[] in the Tuple contains the encrypted bytes of the symmetric key and the name of the algorithm used. Save these as meta data with your ciphertext.
Unwrap / Decrypt AES key
Call using the same key (key version matters), algoName is the name of the algorithm used to wrap the key (e.g. "RSA-OAEP").
// Retrieve the IKey by Key ID
// Unwrap Key
byte[] aesKey = rsa.UnwrapKeyAsync(wrappedKeyBytes, algoName, CancellationToken.None).GetAwaiter().GetResult();
Other details to think about are the Key backup/recovery and key rotation.