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);
Related
I am trying to generate a client assertion from a X509Certificate2 (here is the documentation). The fact is that my code is working perfectly when I get the certificate from a file as follows :
string signinCertPath = Path.Combine(Directory.GetCurrentDirectory(), "Cert", "anAwesomeCertificate.pfx");
X509Certificate2 certificate = new X509Certificate2(signinCertPath, "MySuperCertificatePassword", X509KeyStorageFlags.EphemeralKeySet);
string clientAssertion = GenerateClientAssertion(certificate, tenantId, clientId); //clientAssertion handles the value I am looking for.
Here is the GenerateClientAssertion method, that I've copied from the MSAL documentation :
private string GenerateClientAssertion(X509Certificate2 certificate, string tenantId, string clientId)
{
// Get the RSA with the private key, used for signing.
var rsa = certificate.GetRSAPrivateKey();
//alg represents the desired signing algorithm, which is SHA-256 in this case
//x5t represents the certificate thumbprint base64 url encoded
var header = new Dictionary<string, string>()
{
{ "alg", "RS256"},
{ "typ", "JWT" },
{ "x5t", Base64UrlEncode(certificate.GetCertHash()) }
};
var claims = GetClaims(tenantId, clientId);
var headerBytes = JsonConvert.SerializeObject(header);
var claimsBytes = JsonConvert.SerializeObject(claims);
string token = Base64UrlEncode(Encoding.UTF8.GetBytes(Newtonsoft.Json.Linq.JObject.FromObject(header).ToString())) + "." + Base64UrlEncode(Encoding.UTF8.GetBytes(Newtonsoft.Json.Linq.JObject.FromObject(claims).ToString()));
string signature = Base64UrlEncode(rsa.SignData(Encoding.UTF8.GetBytes(token), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
string signedClientAssertion = string.Concat(token, ".", signature);
return signedClientAssertion;
}
The problem is that I am not able to access the certificate file in real conditions, that is why I copied it in a byte[] and try to do the same thing by creating the certificate with the byte[], like that :
X509Certificate2 certificate = new X509Certificate2(variables.cert, "MySuperCertificatePassword", X509KeyStorageFlags.EphemeralKeySet); //variables.cert is the byte[]
string clientAssertion = GenerateClientAssertion(certificate, tenantId, clientId);
And here comes the issue : my code throw me an error as the rsa variable is not set to an instance of an object, meaning that I am not getting the certificate private key.
So does anybody know how I can get this private key?
Any contribution would be nice. Thanks by advance.
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).
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?
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);
}
I'm not able to sign data with the Service Application private key I downloaded from the Google Developer console. I get the following error:
OAuthTests.TestCrypto.testSha256SignWithGoogleKey:
System.Security.Cryptography.CryptographicException : Invalid algorithm specified.
at System.Security.Cryptography.CryptographicException.ThrowCryptogaphicException(Int32 hr)
at System.Security.Cryptography.Utils._SignValue(SafeKeyHandle hKey, Int32 keyNumber, Int32 calgKey, Int32 calgHash, Byte[] hash, Int32 dwFlags)
at System.Security.Cryptography.RSACryptoServiceProvider.SignHash(Byte[] rgbHash, String str)
at OAuthTests.TestCrypto.testSha256SignWithGoogleKey() in e:\Development\MiscellaneousProjects\RSSNewsFeeder\Oauth\OAuthTests.cs:line 43
Yes, I've asked this question before but not getting much help and since Stack's forum model is not easy to add onto an existing thread it seems that my best best to reword the question is to do just do that; reword and ask a new question.
I have written three unit tests (code below). The first unit test shows that I can sign and verify data using RSACryptoServiceProvider with SHA256, but this test does not use my Google certificate's private key.
When I use the Google's private key certificate and test (2nd test below), the code errors (error message above).
The 3rd test demonstrates using Google's private key and testing using SHA1 and this works! But not valid according to the specs.
Is there something wrong with the code below or is there something wrong with the certificate, or perhaps its my OS or other environmental issue? I'm developing in Windows C# 3.5 on a Windows 8.1 machine.
** THIS WORKS **
Not using Google Certificate
var cert = new X509Certificate2(#"E:\Development\MiscellaneousProjects\RSSNewsFeeder\Samples\9d16ba9bd04468b4cd0dd241e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
byte[] signature = rsa.SignData(data, "SHA256");
if (rsa.VerifyData(data, "SHA256", signature))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
** THIS FAILS **
Using Google certificate and SHA256
Fails At: byte[] signature = rsa.SignData(data, "SHA256");
[Test]
public void testSha256SignWithGoogleKey()
{
var cert = new X509Certificate2(#"....41e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PublicKey.Key)
{
byte[] signature = rsa.SignData(data, "SHA256");
if (rsa.VerifyData(data, "SHA256", signature))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
** THIS WORKS **
Using Google certificate but SHA1
[Test]
public void testShaSignWithGoogleKey()
{
var cert = new X509Certificate2(#"....dd241e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
byte[] signature = rsa.SignData(data, "SHA1");
if (rsa.VerifyData(data, "SHA1", signature))
{
Console.WriteLine("RSA-SHA1 signature verified");
}
else
{
Console.WriteLine("RSA-SHA1 signature failed to verify");
}
}
}
First of all, there's a mistake in your example #2: you are trying to use public key for signing. And you should get the error: "Object contains only the public half of a key pair. A private key must also be provided."
But I suppose it was just a copy/paste mistake, and you already tried with private key.
The RSACryptoServiceProvider obtained from Google's certificate PrivateKey uses "Microsoft Base Cryptographic Provider v1.0", while newly created RSACryptoServiceProvider object uses "Microsoft Enhanced RSA and AES Cryptographic Provider".
The trick to workaround this is to export the bare math from cert's RSACSP to a new RSACSP object:
[Test]
public void testSha256SignWithGoogleKey()
{
var cert = new X509Certificate2(#"....41e34b980643fd5b21-privatekey.p12", "notasecret", X509KeyStorageFlags.Exportable);
byte[] data = new byte[] { 0, 1, 2, 3, 4, 5 };
using (RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PrivateKey)
{
using (RSACryptoServiceProvider myRsa = new RSACryptoServiceProvider())
{
myRsa.ImportParameters(rsa.ExportParameters(true));
byte[] signature = myRsa.SignData(data, "SHA256");
if (myRsa.VerifyData(data, "SHA256", signature))
{
Console.WriteLine("RSA-SHA256 signature verified");
}
else
{
Console.WriteLine("RSA-SHA256 signature failed to verify");
}
}
}
}