I'm using self signed certificate ECDH_secP384r1 for signing token. Here is the PowerShell that I create the certificate:
$Cert = New-SelfSignedCertificate -certstorelocation cert:\localmachine\my -dnsname $Certname -NotAfter $ExpireDate -KeyAlgorithm ECDH_secP384r1
Now in my .net core application first I load the certificate:
private readonly string _certificateSubjectName;
public X509Certificate2 GetSigningCertificate()
{
using (var store = new X509Store(StoreLocation.LocalMachine))
{
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, _certificateSubjectName, false);
return certificates[0];
}
}
And also I can Get the ECDsa private key like
ECDsa privateKey = signingCertificate.GetECDsaPrivateKey();
ECDsa publicKey = signingCertificate.GetECDsaPublicKey()
But how could I have byte array of these keys?
For Rsa I could use:
public byte[] GetPrivateKey(X509Certificate2 certificate)
{
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)certificate.PrivateKey;
MemoryStream memoryStream = new MemoryStream();
TextWriter streamWriter = new StreamWriter(memoryStream);
PemWriter pemWriter = new PemWriter(streamWriter);
AsymmetricCipherKeyPair keyPair = DotNetUtilities.GetRsaKeyPair(rsa);
pemWriter.WriteObject(keyPair.Private);
streamWriter.Flush();
byte[] byteArray = memoryStream.GetBuffer();
return byteArray;
}
But how about ECDsa? any idea?
Related
I've been since yesterday trying a lot of the "solutions" on StackOverflow, but none seems to work.
Given a .pfx certificate with a private key I need to sign a byte array (firmware version of the tracking unit we use).
Here are the things I've tried:
private byte[] generateSignature(byte[] data, X509Certificate2 certificate)
{
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(certificate.PrivateKey.ToXmlString(true));
return key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));
}
And also:
private byte[] generateSignature(byte[] data, X509Certificate2 certificate)
{
string alg = CryptoConfig.MapNameToOID("SHA256");
RSACryptoServiceProvider rsaProvider = (RSACryptoServiceProvider)certificate.PrivateKey;
return rsaProvider.SignData(orig, alg);
}
And also:
private byte[] generateSignature(byte[] data, X509Certificate2 certificate)
{
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
byte[] hash;
using (SHA256 sha256 = SHA256.Create())
{
hash = sha256.ComputeHash(data);
}
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(rsa);
RSAFormatter.SetKey(certificate.PrivateKey);
RSAFormatter.SetHashAlgorithm("SHA256");
return RSAFormatter.CreateSignature(hash);
}
And finally:
private byte[] generateSignature(byte[] data, X509Certificate2 certificate)
{
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)certificate.PrivateKey;
SHA1Managed sha1 = new SHA1Managed();
SHA256Managed sha256 = new SHA256Managed();
byte[] hash = sha256.ComputeHash(data);
csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA256"));
}
On all of these I get the error:
System.Security.Cryptography.CryptographicException:
'Invalid algorithm specified.'
I know my key is 256hash compatible because I've used the command:
openssl x509 -in C:\cert.pfx -text -noout
And the Signature Algorithm was sha256WithRSAEncryption.
Currently learning about reading in the certificate from the windows store.
then using the private and public keys to encrypt a string then decrypt it.
I have a certificate in my store which has the public and private key assigned to it.
I'm reading in the public key fine using
X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
store.Open(OpenFlags.ReadOnly);
foreach (X509Certificate2 certificate in store.Certificates)
{
//TODO's
byte[] encodedPublicKey = certificate.PublicKey.EncodedKeyValue.RawData;
File.WriteAllLines(filePath, new[] {
"-----BEGIN PUBLIC KEY-----",
Convert.ToBase64String(encodedPublicKey, Base64FormattingOptions.InsertLineBreaks),
"-----END PUBLIC KEY-----"
});
publicKey = System.IO.File.ReadAllText(filePath);
encryptedWithPublic = encryption.EncryptWithPublic(input, publicKey);
}
store.Close();
i then call this method to encrypt
var bytesToEncrypt = Encoding.UTF8.GetBytes(clearText);
var encryptEngine = new Pkcs1Encoding(new RsaEngine());
using (var txtreader = new StringReader(publicKey))
{
var keyParameter = (AsymmetricKeyParameter)new PemReader(txtreader).ReadObject();
encryptEngine.Init(true, keyParameter);
}
var encrypted = Convert.ToBase64String(encryptEngine.ProcessBlock(bytesToEncrypt, 0, bytesToEncrypt.Length));
return encrypted;
Note - when i create my own public key the above method works, but when i use a public key from the keystore it is falling with
Additional information: Unknown object in GetInstance:
Org.BouncyCastle.Asn1.DerInteger
clearly i'm doing something wrong when i'm reading in from the store but i've not managed to find any help on here with this issue
The problem is the following:
I generate the key in Android (Xamarin.Droid):
public IPublicKey CreateKey(string keyID)
{
/*KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
keyPairGenerator.initialize(
new KeyGenParameterSpec.Builder(
"key1",
KeyProperties.PURPOSE_SIGN)
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PSS)
.build());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
Signature signature = Signature.getInstance("SHA256withRSA/PSS");
signature.initSign(keyPair.getPrivate());
// The key pair can also be obtained from the Android Keystore any time as follows:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
PrivateKey privateKey = (PrivateKey)keyStore.getKey("key1", null);
PublicKey publicKey = keyStore.getCertificate("key1").getPublicKey();*/
//App.Current.MainPage.DisplayAlert("Info", "Creating a new key pair", "Ok");
// UTILIZANDO RSA
KeyPairGenerator kpg =
KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, KEYSTORE_NAME);
kpg.Initialize(
new KeyGenParameterSpec.Builder(keyID,
KeyStorePurpose.Sign)
.SetSignaturePaddings(KeyProperties.SignaturePaddingRsaPss)
.SetDigests(KeyProperties.DigestSha1)
.Build()
);
KeyPair keyPair = kpg.GenerateKeyPair();
Log.Debug(TAG, "New key created for fingerprint authentication");
return keyPair.Public;
}
Then i generate a signature:
KeyStore.PrivateKeyEntry PKentry =
(KeyStore.PrivateKeyEntry)_keystore.GetEntry(keyID, null);
IPublicKey pk = (IPublicKey)PKentry.Certificate.PublicKey;
//this.pk = pk;
privKey = PKentry.PrivateKey;
//cipher.Init(Cipher.EncryptMode, privKey);
//byte[] output = cipher.DoFinal(Encoding.UTF8.GetBytes(input));
//String s = new string(cipher.DoFinal(input));
// signature
Signature sig = Signature.GetInstance("SHA1withRSA/PSS");
sig.InitSign(privKey);
byte[] inputDataToSign = Encoding.UTF8.GetBytes(input);
sig.Update(inputDataToSign);
byte[] signatureBytes = sig.Sign();
And i send the key and the signature to a ASP.net wep API 2 server.
Client side response generation:
RegistrationResponse registrationResponse = new RegistrationResponse();
string fcparams = Utils.Base64Encode(JsonConvert.SerializeObject(finalChallengeParams));
registrationResponse.fcParams = fcparams;
byte[] signedData = sign(fcparams, registrationRequest.username, facetID);
registrationResponse.signedData = signedData;
registrationResponse.Base64key = convertPublicKeyToString(publicKey);
...
...
private string convertPublicKeyToString(IPublicKey publicKey)
{
string publicKeyString = Base64.EncodeToString(publicKey.GetEncoded(), 0);
return publicKeyString;
}
I send it using Refit Nugget.
And this is the code i use when i receive the HTTPRequest on server side:
[Route("regResponse/")]
[HttpPost]
public IHttpActionResult ProcessClientRegistrationResponse([FromBody] RegistrationResponse registrationResponse)
{
//byte[] publicKeyBytes = Convert.FromBase64String(registrationResponse.Base64key);
byte[] publicKeyBytes = registrationResponse.Base64key;
AsymmetricKeyParameter asymmetricKeyParameter = PublicKeyFactory.CreateKey(publicKeyBytes);
RsaKeyParameters rsaKeyParameters = (RsaKeyParameters)asymmetricKeyParameter;
RSAParameters rsaParameters = new RSAParameters();
rsaParameters.Modulus = rsaKeyParameters.Modulus.ToByteArrayUnsigned();
rsaParameters.Exponent = rsaKeyParameters.Exponent.ToByteArrayUnsigned();
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
rsa.ImportParameters(rsaParameters);
/*****/
string alg = rsa.SignatureAlgorithm;
byte[] signedData = registrationResponse.signedData;
byte[] fcParamsBytes = Encoding.UTF8.GetBytes(registrationResponse.fcParams);
RSACng rsaCng = new RSACng();
rsaCng.ImportParameters(rsaParameters);
SHA1Managed hash = new SHA1Managed();
byte[] hashedData;
hashedData = hash.ComputeHash(signedData);
/*********/
bool rsaCngDataOk1 = rsaCng.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk2 = rsaCng.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk3 = rsaCng.VerifyData(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngDataOk4 = rsaCng.VerifyData(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool rsaCngHashOk1 = rsaCng.VerifyHash(hashedData, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pss);
bool dataOK1 = rsa.VerifyData(fcParamsBytes, new SHA1CryptoServiceProvider(), signedData);
bool dataOk2 = rsa.VerifyData(fcParamsBytes, signedData, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
bool hashOk = rsa.VerifyHash(hashedData, CryptoConfig.MapNameToOID("SHA1"), signedData);
return Ok(true);
}
EVERY bool is wrong. I think the problem is clearly on the public key.
The questions are,
does the method publickey.encode() do what i think? I think it converts my public key to a byte[] representation (source: Android developer Key Info)
do i convert the received byte[] representation of the key to a correct RSA key?
Is there any problem on algorithms? I don't think so but we never know...
I don't find the solution. I searched for ways to import public keys from strings in .net or c# and for ways to export Android Public key to string or byte[] but there's no much help for this concrete questions...
#James K Polk gave me the solution.
Apparently C# doesn't work well with PSS padding. I just had to change it to PKCS1. And i changed to digest algorithm too to SHA512.
I wrote two methods for signing using RSA and SHA256, the first one with OpenSSL library and the second one with Microsoft Cryptography library.
OpenSSL implementation:
private string PasswordHandler(bool verify, object userdata)
{
return userdata.ToString();
}
private string Sign(string signParams)
{
var privateCertPath = HttpContext.Current.Server.MapPath(#"~\certificate.pem");
string privateKey;
using (StreamReader sr = new StreamReader(privateCertPath))
{
privateKey = sr.ReadToEnd();
}
OpenSSL.Crypto.RSA rsa = OpenSSL.Crypto.RSA.FromPrivateKey(new BIO(privateKey), PasswordHandler, _password);
//hash method
MessageDigest md = MessageDigest.SHA1;
BIO b = new BIO(signParams);
CryptoKey ck = new CryptoKey(rsa);
byte[] res1 = MessageDigestContext.Sign(md, b, ck);
return Uri.EscapeDataString(System.Convert.ToBase64String(res1));
}
Cryptography implementation:
private string Sign(string data)
{
var privateCertPath = HttpContext.Current.Server.MapPath(#"~\certificate.pfx");
X509Certificate2 privateCert = new X509Certificate2(privateCertPath, _password, X509KeyStorageFlags.Exportable);
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(privateKey.ExportParameters(true));
// Get the bytes to be signed from the string
var bytes = System.Text.Encoding.UTF8.GetBytes(data);
//const string sha256Oid = "2.16.840.1.101.3.4.2.1";
//HashAlgorithm algorithm = new SHA256CryptoServiceProvider();
//byte[] hashBytes = algorithm.ComputeHash(bytes);
//byte[] signature = privateKey1.SignHash(hashBytes, sha256Oid);
byte[] signature = privateKey1.SignData(bytes, "SHA256");
// Base 64 encode the sig so its 8-bit clean
return Convert.ToBase64String(signature);
}
Signing with OpenSSL works, generates valid digital signature but signing with Cryptography lib generates invalid signature so my question is what I implemented wrong?
I tried to use different encoding but it did not help. Certificates are generated correctly.
It might by also useful to tell basic info about the .pem certificate:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC
I'm working on some code that creates a X509certificate and a public/private key pair. The public key is added to the certificate and it is sent to an CA which signs it.
The returned certificate is then accessed through the System.Security.Cryptography.X509Certificates.X509Certificate2 class. Now I want to use this certificate to initiate a secure connection with other clients. Therefore I use the SslStream class. To start the SSL Handshake I use this method:
server.AssociatedSslStream.AuthenticateAsServer(
MyCertificate, // Client Certificate
true, // Require Certificate from connecting Peer
SslProtocols.Tls, // Use TLS 1.0
false // check Certificate revocation
);
This method requires that the private key is associated with the certificate. Of course the certificate returned by the CA does not contain a private key. But it is stored as .key file on the harddrive. The X509Certificate2 class has a property called PrivateKey which I guess will associate a private key with the certificate, but I can't find a way to set this property.
Is there any way I can associate the private key with the .net X509 class?
You can save yourself the hassle of copy-pasting all that code and store the private key next to the certificate in a pfx/pkcs#12 file:
openssl pkcs12 -export -in my.cer -inkey my.key -out mycert.pfx
You'll have to supply a password, which you have to pass to the constructor of X509Certificate2:
X509Certificate2 cert = new X509Certificate2("mycert.pfx","password");
For everyone else with the same problem, I found a neat little piece of code that let's you do exactly that:
http://www.codeproject.com/Articles/162194/Certificates-to-DB-and-Back
byte[] certBuffer = Helpers.GetBytesFromPEM(publicCert, PemStringType.Certificate);
byte[] keyBuffer = Helpers.GetBytesFromPEM(privateKey, PemStringType.RsaPrivateKey);
X509Certificate2 certificate = new X509Certificate2(certBuffer, password);
RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;
EDIT: The code for the Helper method (which otherwise requires a codeproject login) is as follows:
public static byte[] GetBytesFromPEM(string pemString, PemStringType type)
{
string header; string footer;
switch (type)
{
case PemStringType.Certificate:
header = "-----BEGIN CERTIFICATE-----";
footer = "-----END CERTIFICATE-----";
break;
case PemStringType.RsaPrivateKey:
header = "-----BEGIN RSA PRIVATE KEY-----";
footer = "-----END RSA PRIVATE KEY-----";
break;
default:
return null;
}
int start = pemString.IndexOf(header) + header.Length;
int end = pemString.IndexOf(footer, start) - start;
return Convert.FromBase64String(pemString.Substring(start, end));
}
Update
As of .NET 5 you can simply use CreateFromPem(ReadOnlySpan, ReadOnlySpan):
Creates a new X509 certificate from the contents of an RFC 7468
PEM-encoded certificate and private key.
example:
X509Certificate2 cert = X509Certificate2.CreateFromPem(
certPem, //The text of the PEM-encoded X509 certificate.
keyPem //The text of the PEM-encoded private key.
);
Or if you have a string with both the cert and its private key, you can pass it in for both the cert arg and the key arg:
X509Certificate2 cert = X509Certificate2.CreateFromPem(
certPem, //The text of the PEM-encoded X509 certificate.
certPem// The text of the PEM-encoded private key.
);
my solution
byte[] PublicCertificate = Encoding.Unicode.GetBytes("-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----");
var publicCertificate = new X509Certificate2(PublicCertificate );
byte[] PrivateKey = Convert.FromBase64String("MIIEvQIBA...=");
using var rsa = RSA.Create();
rsa.ImportPkcs8PrivateKey(PrivateKey, out _);
publicCertificate = publicCertificate.CopyWithPrivateKey(rsa);
publicCertificate = new X509Certificate2(publicCertificate.Export(X509ContentType.Pkcs12));
var client = new RestClient("api_url");
client.ClientCertificates = new X509Certificate2Collection();
client.ClientCertificates.Add(publicCertificate);
.NET 5+
X509Certificate2.CreateFromPemFile(certPath, keyPath);
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2.createfrompemfile?view=net-5.0#System_Security_Cryptography_X509Certificates_X509Certificate2_CreateFromPemFile_System_String_System_String_
for .NET Framework 4.8
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
...
private X509Certificate2 GetCert(string clientCertFile, string privateKeyFile) {
var tempCertificate = new X509Certificate2(clientCertFile);
StreamReader reader = new StreamReader(privateKeyFile);
PemReader pemReader = new PemReader(reader);
AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
AsymmetricKeyParameter privateKey = keyPair.Private;
RSA rsa = DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters) privateKey);
tempCertificate = tempCertificate.CopyWithPrivateKey(rsa);
return new X509Certificate2(tempCertificate.Export(X509ContentType.Pkcs12));
}
My .net 45 solution thanks to Lee Taylor and Neano
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Parameters;
using System.IO;
using System.Security.Cryptography.X509Certificates;
private X509Certificate2 GetCert(string certPath,string keyPath)
{
X509Certificate2 cert = new X509Certificate2(certPath);
StreamReader reader = new StreamReader(keyPath);
PemReader pemReader = new PemReader(reader);
RsaPrivateCrtKeyParameters keyPair=(RsaPrivateCrtKeyParameters)pemReader.ReadObject();
RSA rsa = DotNetUtilities.ToRSA(keyPair);
cert.PrivateKey = rsa;
return new X509Certificate2(cert.Export(X509ContentType.Pfx));
}