Damo
Damo

Reputation: 2070

Encrypt a string using local public PEM file, decrypt using private key in Azure KayVault

I'm trying to write a proof of concept whereby we can encrypt a string using a public key locally and decrypt the string with the associated private key in Azure Key Vault.

I can successfully do this if I encrypt and decrypt using keys directly from Azure KeyVault; however, our use case is that we want to download and store the public key (manually) from Azure KeyVault to encrypt.

In my sample code below, I can encrypt using the local public PEM file. When I try and decode using the associated private key in Azure, I get the error:

System.AggregateException: 'One or more errors occurred. (Error occurred while decoding OAEP padding.
Status: 400 (Bad Request)
ErrorCode: BadParameter

I get that RSAEncryptionPadding and EncryptionAlgorithm in the encrypt and decrypt may be incompatible, but I don't know where to go from here.

Code:

using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;
using Azure.Identity;
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;


string input = "SomeString";
var encryptStringAsync = EncryptStringAsync(input);

var valenc = encryptStringAsync.Result;

Console.WriteLine(valenc);

var decryptStringAsync = DecryptStringAsync(valenc);

var valueenc = decryptStringAsync.Result;

Console.WriteLine(valueenc);

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

    using (RSA rsa = GetRSAFromPemFile())
    {
        byte[] encryptedData = rsa.Encrypt(inputAsByteArray, RSAEncryptionPadding.OaepSHA256);
        return Convert.ToBase64String(encryptedData);
    }

    RSA GetRSAFromPemFile()
    {
        string publicKey = File.ReadAllText("C:\\temp\\20231117.pem");

        var rsaPublicKey = RSA.Create();
        rsaPublicKey.ImportFromPem(publicKey);

        return rsaPublicKey;
    }
}


static async Task<string> DecryptStringAsync(string input)
{

    string keyVaultName = "xxx";
    string keyVaultUri = "https://" + keyVaultName + ".vault.azure.net";
    string keyVaultKeyName = "xxx";
    
    var client = new KeyClient(new Uri(keyVaultUri), new DefaultAzureCredential());
    KeyVaultKey key = await client.GetKeyAsync(keyVaultKeyName).ConfigureAwait(false);
    var cryptoClient = new CryptographyClient(key.Id, new DefaultAzureCredential());
    
    byte[] inputAsByteArray = Convert.FromBase64String(input);
    DecryptResult decryptResult = await cryptoClient.DecryptAsync(EncryptionAlgorithm.RsaOaep, inputAsByteArray).ConfigureAwait(false);
    return Encoding.Default.GetString(decryptResult.Plaintext);
}

Upvotes: 2

Views: 438

Answers (1)

Topaco
Topaco

Reputation: 49251

When using RSA with OAEP, the OAEP parameters for decryption must correspond to those for encryption. This applies in particular to the two digests, the OAEP digest and the MGF1 digest. Another parameter is the OAEP label, which is generally always left blank. For RSA with OAEP see RFC 8017, 7.1. RSAES-OAEP.

In the posted code, however, the digests differ for encryption and decryption. In EncryptStringAsync(), RSAEncryptionPadding.OaepSHA256 is used, which applies SHA-256 for both digests, while in DecryptStringAsync() EncryptionAlgorithm.RsaOaep is used (see also here), which applies SHA-1 for both digests.

The fix is therefore to use RSAEncryptionPadding.OaepSHA1 in EncryptStringAsync() or alternatively EncryptionAlgorithm.RsaOaep256 in DecryptStringAsync() so that the same OAEP parameters are used on both sides.

Upvotes: 1

Related Questions