I'm trying Microsoft docs example to encrypt/decrypt a text using Azure Key Vault SDK.
I created key manually via Azure Portal. Encryption part succeeds, but decrypt throws 'Key does not exist' exception. I can't understand why, because key exists, it has decrypt as a permitted operation and I can confirm that in VS in a debug mode (KeyVaultKey -> KeyOperations lists 'Decrypt').
This is the full code:
static void Main(string[] args)
{
var keyVaultKeyIdentifier = new KeyVaultKeyIdentifier(new Uri("key-url"));
var keyClient = new KeyClient(keyVaultKeyIdentifier.VaultUri, new DefaultAzureCredential());
var keyVaultKey = keyClient.GetKey(keyVaultKeyIdentifier.Name).Value;
var cryptoClient = new CryptographyClient(keyVaultKey.Key);
byte[] plaintext = Encoding.UTF8.GetBytes("A single block of plaintext");
// encrypt the data using the algorithm RSAOAEP
EncryptResult encryptResult = cryptoClient.Encrypt(EncryptionAlgorithm.RsaOaep, plaintext);
// decrypt the encrypted data.
// **Exception is thrown on this line**
DecryptResult decryptResult = cryptoClient.Decrypt(EncryptionAlgorithm.RsaOaep, encryptResult.Ciphertext);
}
For the reference the stack trace of an exception:
at System.Security.Cryptography.RSAImplementation.RSACng.EncryptOrDecrypt(SafeNCryptKeyHandle key, ReadOnlySpan`1 input, AsymmetricPaddingMode paddingMode, Void* paddingInfo, Boolean encrypt)
at System.Security.Cryptography.RSAImplementation.RSACng.EncryptOrDecrypt(Byte[] data, RSAEncryptionPadding padding, Boolean encrypt)
at System.Security.Cryptography.RSAImplementation.RSACng.Decrypt(Byte[] data, RSAEncryptionPadding padding)
at Azure.Security.KeyVault.Keys.Cryptography.RsaCryptographyProvider.Decrypt(Byte[] data, RSAEncryptionPadding padding)
at Azure.Security.KeyVault.Keys.Cryptography.RsaCryptographyProvider.Decrypt(DecryptParameters parameters, CancellationToken cancellationToken)
at Azure.Security.KeyVault.Keys.Cryptography.CryptographyClient.Decrypt(DecryptParameters decryptParameters, CancellationToken cancellationToken)
at Azure.Security.KeyVault.Keys.Cryptography.CryptographyClient.Decrypt(EncryptionAlgorithm algorithm, Byte[] ciphertext, CancellationToken cancellationToken)
at DotNetFiveCrypto.Program.Main(String[] args) in C:\Users\mike\Documents\Visual Studio 2019\Projects\DotNetFiveCrypto\Program.cs:line 32
I'm using .NET 5 on Windows 10, referenced SDK packages:
<PackageReference Include="Azure.Identity" Version="1.4.1" />
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.2.0" />
You are instantiating CryptographyClient using the public part of your key - so the SDK creates a local client, which is only capable of encrypting.
Since the private part is never exposed by Key Vault, you need to instantiate CryptographyClient using the Id of your key instead, so that it creates a remote client that delegates both encryption and decryption to Key Vault REST API.
Here's the fix:
static void Main(string[] args)
{
var keyVaultKeyIdentifier = new KeyVaultKeyIdentifier(new Uri("key-url"));
var credential = new DefaultAzureCredential();
var keyClient = new KeyClient(keyVaultKeyIdentifier.VaultUri, credential);
var keyVaultKey = keyClient.GetKey(keyVaultKeyIdentifier.Name).Value;
var cryptoClient = new CryptographyClient(keyVaultKey.Id, credential);
byte[] plaintext = Encoding.UTF8.GetBytes("A single block of plaintext");
EncryptResult encryptResult = cryptoClient.Encrypt(EncryptionAlgorithm.RsaOaep, plaintext);
DecryptResult decryptResult = cryptoClient.Decrypt(EncryptionAlgorithm.RsaOaep, encryptResult.Ciphertext);
}
Here's an additional encryption/decryption sample and here the source code where it switches between local and remote clients.
I don't know the use case in question, but you could build a local crypto client for encryption (faster) and use the remote one for decryption (slower).
Related
I use provided code in this document. The difference here is only values of some account variables. Algorithm used for generate private key (that stored in private.pem file) is RSA_2048.
The main goal is achive JWT with PS256 encryption algorithm.
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using Jose;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
class Program
{
static void Main(string[] args)
{
var serviceAccountId = "myServiceAccountId";
var keyId = "myKeyId
var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var headers = new Dictionary<string, object>()
{
{ "kid", keyId }
};
var payload = new Dictionary<string, object>()
{
{ "aud", "someAudience" },
{ "iss", serviceAccountId },
{ "iat", now },
{ "exp", now + 3600 }
};
RsaPrivateCrtKeyParameters privateKeyParams;
using (var pemStream = File.OpenText("private.pem"))
{
privateKeyParams = new PemReader(pemStream).ReadObject() as RsaPrivateCrtKeyParameters;
}
using (var rsa = new RSACryptoServiceProvider())
{
rsa.ImportParameters(DotNetUtilities.ToRSAParameters(privateKeyParams));
string encodedToken = Jose.JWT.Encode(payload, rsa, JwsAlgorithm.PS256, headers);
}
}
}
On line:
string encodedToken = Jose.JWT.Encode(payload, rsa, JwsAlgorithm.PS256, headers);
I receive an exception:
System.AggregateException: One or more errors occurred. (Specified padding mode is not valid for this algorithm.)
---> System.Security.Cryptography.CryptographicException: Specified padding mode is not valid for this algorithm.
at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] hash, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
at System.Security.Cryptography.RSA.SignData(Byte[] data, Int32 offset, Int32 count, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
at System.Security.Cryptography.RSA.SignData(Byte[] data, HashAlgorithmName hashAlgorithm, RSASignaturePadding padding)
at Jose.RsaPssUsingSha.Sign(Byte[] securedInput, Object key)
at Jose.JWT.EncodeBytes(Byte[] payload, Object key, JwsAlgorithm algorithm, IDictionary`2 extraHeaders, JwtSettings settings)
at Jose.JWT.Encode(String payload, Object key, JwsAlgorithm algorithm, IDictionary`2 extraHeaders, JwtSettings settings)
at Jose.JWT.Encode(Object payload, Object key, JwsAlgorithm algorithm, IDictionary`2 extraHeaders, JwtSettings settings)
I use jwt-jose package for encoding.
If you're running on .Net Framework on Windows just replace RSACryptoServiceProvider with RSACng
using (var rsa = new RSACng())
If you're on .Net Core it becomes a bit more complicated as it relies on OS crypto libraries. You should detect platform in your code and use either RSACng for Windows or RSAOpenSsl on Linux (available on .Net 6)
using (RSA rsa = new RSAOpenSsl())
I confront the same error, what I do is like follow
var cert = new X509Certificate2(certificate.Value.Cer);
using var csp = (RSA) cert.PublicKey.Key;
var bytesEncrypted = csp.Encrypt(content, RSAEncryptionPadding.OaepSHA256);
All went fine in .Net 6 but throws error when using framework 4.8.
The previous answer almost takes me there.
I find an workaround as below:
var cert = new X509Certificate2(Convert.FromBase64String(content.CertificateContentRaw));
var csp = new RSACng();
var xml = cert.GetRSAPublicKey().ToXmlString(false);
csp.FromXmlString(xml);
I have a client who encrypts data using the public key of a SSL certificate. Details:
IIS 10.
.Net 4.8, C#
SSL Certificate - 4096 bit, issued by CA.
Client connects to my web API and sends encrypted data as parameter as a raw string using the public key of above certificate installed on my server.
Client uses RSAEncryptionPadding.OaepSHA256 for padding.
I have tried (Code 1):
public string DecryptRsa(string encrypted, X509Certificate2 x509Certificate2, RSAEncryptionPadding rSAEncryptionPadding)
{
var text = string.Empty;
using (RSA csp = (RSA)x509Certificate2.PrivateKey)
{
byte[] bytesEncrypted = Convert.FromBase64String(encrypted);
byte[] bytesDecrypted = csp.Decrypt(bytesEncrypted, rSAEncryptionPadding);
text = Encoding.UTF8.GetString(bytesDecrypted);
}
return text;
}
This gives error: Specified padding mode is not valid for this algorithm.
Code 2:
public string DecryptRsa1(string encrypted, X509Certificate2 x509Certificate2, RSAEncryptionPadding rSAEncryptionPadding)
{
var text = string.Empty;
var privateKey = x509Certificate2.GetRSAPrivateKey();
var bytesData = Convert.FromBase64String(encrypted);
var dataByte = privateKey.Decrypt(bytesData,rSAEncryptionPadding);
text = Encoding.UTF8.GetString(dataByte);
return text;
}
This gives the error: Parameter is incorrect.
I have tested using my public key for encryption and private key for decryption using OaepSHA1 padding and both above code work fine.
What am I not getting/doing wrong?
I have an RSA key stored in the KeyVault (not a secret, a key). Using the .net KeyVaultClient I can retrieve the key.
However, when I try to decrypt the key so it can be used inside one of our applications, I am receiving an error (I have tried several methods of decrypting the key each is highlighted below, and each generates a different error message / exception).
I have tried a several methods. I will outline each, hopefully without disclosing any of our sensitive information.
KeyBundle keyBundle = await keyClient.GetKeyAsync(fullUri);
Where
(i) keyClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessToken)));
(ii) fullUri is "https://[mycompany].vault.azure.net/keys/[keyidentifier]"
I receive a KeyBundle object and inside that KeyBundle object I have the JsonWebKey which contains the Modulus and Exponent both as byte arrays.
Once I have these values I have tried decrypting the key using the following methods.
Using the keyClient.DecryptAsync(keyBundle.KeyIdentifier.ToString(), JsonWebEncryptionAlgorithm.RSA15, keyBundel.Key.N).GetAwaiter().GetResult(); method.
I have tried changing the algorithm in the above call and receive the same error (KeyVaultErrorException): BadRequest,
Also, I have tried replacing keyBundle.KeyIdentifier.ToString() with keyBundle.Key.Kid (which seems to hold the same value).
Using the KeyVaultClientExtensions.DecryptAsync(keyClient, keyBundle.Key.Kid, "RSAOAEP", keyBundle.Key.N);
I also receive a KeyVaultErrorException - BadRequest.
Using an HttpClient PostAsync event (code below) I have tried calling the Rest APi directly and receive the following exception:
{"error":{"code":"BadParameter","message":"The parameter is
incorrect.\r\n"}}
Code for the Rest Api Call:
keyClient = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(GetAccessToken));
string fullUri = $"{baseUrl}keys/{keyIdentifier}";
KeyBundle keyBundle = await keyClient.GetKeyAsync(fullUri);
JsonWebKey key = keyBundle.Key;
string decryptUri = $"{keyBundle.Key.Kid}/decrypt?api-version=7.0";
HttpResponseMessage response = null;
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", AccessToken);
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
try
{
var uri = new Uri(decryptUri, UriKind.Absolute);
string encryptedText = Convert.ToBase64String(key.N); //Encoding.UTF8.GetString(key.N, 0, key.N.Length);
DecryptionRequestInformation requestInformation = new DecryptionRequestInformation();
requestInformation.alg = "RSA1_5";
requestInformation.value = encryptedText;
var stringContent = new StringContent(JsonConvert.SerializeObject(requestInformation), Encoding.UTF8, "application/json");
response = await client.PostAsync(uri, stringContent);
}
catch (Exception ex)
{
throw ex;
}
}
string encryptionResult = string.Empty;
if (response != null)
{
encryptionResult = response.Content.ReadAsStringAsync().Result;
}
AccessToken is the result from the call to GetAccessToken in the KeyVaultClient callback
I would like to be able to use the decrypted key in one of our applications but am unable to decrypt it.
Could someone please explain what I am missing. I have a feeling that the byte array that is returned from the GetKeyAsync call is not what I think it is (is the RSA Modulus value (key.N) the encrypted key ?) or that I am not encoding the bytes (key.N) correctly to pass to the DecryptAsync method.
Unfortunately, many of the examples online refer to KeyVault secrets and not to keys and I cannot find any examples that seem to work when dealing with Keyvault keys.
What am I missing her please ?
Thanks in advance
Julian
Based on your issue description and the comments, I think you may use the Azure Key Vault in a wrong way.
You can only get the public part of the key which can be used to generate the public key.
The private key will be stored in Key Vault, and will not be able to be retrieved.
So, you can use KeyVaultClient to:
Sign digest with private key
Verify signature with public key
Encrypt data with public key
Decrypt data with private key
The Sign and Decrypt operations can only be performed with KeyVaultClient. The Verify and Encrypt operations can be performed locally, as you can generate the public key with modulus and exponent from the key's public part.
If you do want to get the private key and use it in your application, I think you have to use secret. Secret is just the thing you want: you can store it on Azure safely, and retrieve it when needed.
When the line to extract the public key is executed, an LDAP request is sent:
this.certificate = new X509Certificate2(buffer);
System.Security.Cryptography.X509Certificates.PublicKey key = this.certificate.PublicKey;
50 0.853745000 xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx LDAP 404 searchRequest(1) "" baseObject
...which I believe is authenticating the currently logged on user. I really need to avoid this call, as on a customer system this causes a long delay because of network configuration.
I'm assuming it's attempting to do some authentication around some kind of key store, but as in this case the certificate is all contained in the buffer provided, all I want is for the key to be used without this request being sent.
All I actually want is to create an RSACryptoServiceProvider from the private key in the certificate. I tried a few methods I've found on here involving GetPrivateKey, but struggled to get anything to work.
Thanks in advance!
EDIT Test program:
static void Main( string[] args )
{
var certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(#"E:\Temp\Cert.cer");
System.Security.Cryptography.X509Certificates.PublicKey key = certificate.PublicKey;
}
The certificate I tested with can be found here: Cert.cer
Yes, it's not the strongest signature or key, before I get comments!
Thanks again.
EDIT: I actually worked around this by using a suggestion to use BouncyCastle. I use this to parse the certificate:
X509CertificateParser parser = new X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate cert = parser.ReadCertificate(buffer);
I then extract the modulus and exponent and push them into a Microsoft RSAParameters:
RsaKeyParameters key = (RsaKeyParameters)cert.GetPublicKey();
// Construct a microsoft RSA crypto service provider using the public key in the certificate
RSAParameters param = new RSAParameters();
param.Exponent = key.Exponent.ToByteArrayUnsigned();
param.Modulus = key.Modulus.ToByteArrayUnsigned();
I can then construct the Microsoft RSACryptoServiceProvider from this:
using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
{
provider.ImportParameters(param);
byte[] rsaBlock = provider.Encrypt(preMasterSecret, false);
this.Client.Writer.Write(rsaBlock);
}
I never got any other response, so here's the Bouncycastle implementation I used.
X509CertificateParser parser = new X509CertificateParser();
Org.BouncyCastle.X509.X509Certificate cert = parser.ReadCertificate(buffer);
I then extract the modulus and exponent and push them into a Microsoft RSAParameters:
RsaKeyParameters key = (RsaKeyParameters)cert.GetPublicKey();
// Construct a microsoft RSA crypto service provider using the public key in the certificate
RSAParameters param = new RSAParameters();
param.Exponent = key.Exponent.ToByteArrayUnsigned();
param.Modulus = key.Modulus.ToByteArrayUnsigned();
I can then construct the Microsoft RSACryptoServiceProvider from this:
using (RSACryptoServiceProvider provider = new RSACryptoServiceProvider())
{
provider.ImportParameters(param);
byte[] rsaBlock = provider.Encrypt(preMasterSecret, false);
this.Client.Writer.Write(rsaBlock);
}
PHP code verifies iPizza signature from bank:
$key = openssl_pkey_get_public (file_get_contents ($preferences['bank_certificate']));
if (!openssl_verify ($data, $signature, $key)) {
trigger_error ("Invalid signature", E_USER_ERROR);
}
I tried to convert it to ASP .NET using
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
X509Certificate2 cert = new X509Certificate2(HttpContext.Current.Request.MapPath("~/App_Data/bankert.crt"), "");
RSACryptoServiceProvider rsaCryptoIPT = new RSACryptoServiceProvider();
rsaCryptoIPT.ImportCspBlob(cert.RawData);
if (!rsaCryptoIPT.VerifyData(data, CryptoConfig.MapNameToOID("SHA1"), signature))
throw new InvalidOperationException("Invalid signature from bank ");
but line rsaCryptoIPT.ImportCspBlob(cert.RawData) causes Cryptography.CryptographicException invalid provider version:
StackTrace:
at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
at System.Security.Cryptography.Utils._ImportCspBlob(Byte[] keyBlob, SafeProvHandle hProv, CspProviderFlags flags, SafeKeyHandle& hKey)
at System.Security.Cryptography.Utils.ImportCspBlobHelper(CspAlgorithmType keyType, Byte[] keyBlob, Boolean publicOnly, CspParameters& parameters, Boolean randomKeyContainer, SafeProvHandle& safeProvHandle, SafeKeyHandle& safeKeyHandle)
at System.Security.Cryptography.RSACryptoServiceProvider.ImportCspBlob(Byte[] keyBlob)
...
How to fix ?
bank_certificate file contains
-----BEGIN CERTIFICATE----- MIIDRTCCAq6gAwIBAgIBADANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJFRTEO
....
C82uR/wUZJDw9kj+R1O46/byG8yA+S9FVw==
-----END CERTIFICATE-----
UPDATE: I changed code according to tyranid answer to
var cert = new X509Certificate2(HttpContext.Current.Request.MapPath("~/App_Data/banksert.crt"), "");
var rsaCryptoIPT = (RSACryptoServiceProvider)cert.PublicKey.Key;
var sha1 = new SHA1CryptoServiceProvider();
if (!rsaCryptoIPT.VerifyData(data, sha1, signature))
throw new InvalidOperationException("Invalid signature from bank ");
This code causes Invalid signature from bank exception. Inspecting cert object in shows cert data.
How to fix this so that signature is validated?
Debugger shows that bank cert valid date has expired. Maybe this causes error or VerifyDate second parameter is wrong.
I can successfully sign the data and bank accepts signature using code
SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider();
X509Certificate2 cert = new X509Certificate2(HttpContext.Current.Request.MapPath("~/App_Data/P12File.p12"), "");
RSACryptoServiceProvider rsaCryptoIPT = (RSACryptoServiceProvider)cert.PrivateKey;
byte[] binSignature = rsaCryptoIPT.SignData(binData, sha1);
Verifying bank signature should be reverse to this process, same algorithms are used. How to verify signature ?
Do you have Enhanced crypto provider installed?
Actually, to not depend on crypto providers, you can use other libraries, which implements all the cryptography stuff in native code. I know about EldoS SecureBlackbox (which is commercial), and Bouncy Castle (free one), however there can be other libraries on market.
If the X509Certificate2 has successfully imported the cert file then you get the public key of the cert from the PublicKey.Key parameter.