Encrypt Secrets With A Certificate In Azure Websites

Here's the scenario: you have a secret that needs to be used in your website but you know it's insecure to put the password in your code in plain text.

One easy solution is to manually enter the secret into the web config using the Application Settings page in the portal, but that doesn't give a great local development experience and I'm also not convinced that's totally secure. Instead, I decided to encrypt the secret with a certificate and make sure that certificate is installed on any machine that is running the website (developer machine, Azure, etc.)

I pieced together a lot of different tutorials. This article from TechNet had a lot of what I needed, but I never found a start to end tutorial that gave me all the pieces. So, while I know this example will likely rot as things evolve, I'll post it here to hopefully be useful to someone else.

Disclaimer: I'm not a security guru nor do I work on any of the projects mentioned in this article. Please apply your own security knowledge and research to this and make sure it's a good fit for your scenario too!

Create a key encipherment certificate. From powershell, run the following and note the thumbprint in the output.

New-SelfSignedCertificate -Type DocumentEncryptionCert -KeyUsage DataEncipherment -Subject mysuperdataenciphermentcert -Provider 'Microsoft Enhanced Cryptographic Provider v1.0'

Open the local machine certificate manager. Find the certificate under Personal\Certificates. Right click and choose Export. Be sure to include the private key when you export.

Still in the certificate manager, right click on the cert and choose Mange Private Keys. Add your user account with Read permissions. Without this, I got a "Keyset does not exist" error when I tried to access the private key from code.

In the Azure Portal (portal.azure.com) page for your website, go to "SSL Certificates". Upload the PFX file that you exported in step 2.

Still in the settings for your web app, choose Application Settings and in the App Settings section, add a new entry "WEBSITE_LOAD_CERTIFICATES". The value is the thumbprint of the cert you just uploaded. This setting seems to tell your web site's VM to load that cert. Without this, you won't be able to access the cert from code.

Now you're ready to update your web app code to use the certificate. The code below works on my local machine but when I run it in Azure, I have to change the StoreLocation to CurrentUser. I ended up writing code that would try both stores until it found the cert so that we could run locally or in Azure without changing anything.

 var stringToEncrypt = "Shhh this is my secret";
var certStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
certStore.Open(OpenFlags.ReadOnly);
var cert = certStore.Certificates.Find(X509FindType.FindByThumbprint, "PUTYOURTHUMBPRINTHERE", false)[0];

// Encrypt string with cert
var encrypted = "";
using (var csp = (RSACryptoServiceProvider)cert.PublicKey.Key)
{
 byte[] bytesData = Encoding.UTF8.GetBytes(stringToEncrypt);
 byte[] bytesEncrypted = csp.Encrypt(bytesData, false);
 encrypted = Convert.ToBase64String(bytesEncrypted);
}

// Decrypt with cert
var decrypted = "";
using (var csp = (RSACryptoServiceProvider)cert.PrivateKey)
{
 byte[] bytesEncrypted = Convert.FromBase64String(encrypted);
 byte[] bytesDecrypted = csp.Decrypt(bytesEncrypted, false);
 decrypted = Encoding.UTF8.GetString(bytesDecrypted);
}

I included the encryption code above just in case you need it, but in my scenario, I wanted to encrypt the string locally and then I only do the decryption on the server. It's pretty straightforward to do the encryption via PowerShell. This script prints the encrypted string to the screen and copies it to the clipboard.

 $thumbprint = 'PURYOURTHUMBPRINTHERE'
$cert = Get-Item -Path Cert:\LocalMachine\My\$thumbprint -ErrorAction Stop
$enc = [system.Text.Encoding]::UTF8
$string1 = "Shh this is my secret"
$data1 = $enc.GetBytes($string1)
$result = $cert.PublicKey.Key.Encrypt($data1, $false)
[System.Convert]::ToBase64String($result)
[System.Convert]::ToBase64String($result) | Set-Clipboard

The other people on your team will also need that certificate if they are going to work on the website so save it in a secure location (like Azure KeyVault) and have them install it on their machines.