I'm working with a 3rd party who sends me a certificate via a byte array. They send me 3 strings in an XML document that include, x509Data, SignatureValue, and DigestValue(for debugging).
They want me to validate that the SignatureValue is valid using the certificate public key contained in the x509Data cert. I'm populating the cert fine but when I try to Verify, it always returns false.
Here is my code:
byte[] SignatureValueBytes = Convert.FromBase64String(Signature.SignatureValue);
byte[] x509DataBytes = Convert.FromBase64String(Signature.x509Data);
byte[] DigestValueBytes = Convert.FromBase64String(Signature.DigestValue);
X509Certificate2 cert = new X509Certificate2(x509DataBytes);
using (RSA RSA = (RSA)cert.PublicKey.Key)
{
bool a = RSA.VerifyData(x509DataBytes, SignatureValueBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Signature.* is the string coming from the XML file. Can some kind soul point out where I'm going wrong here?
Your code, as written, is trying to verify that SignatureValueBytes is a signature that signed x509DataBytes using RSASSA-PKCS1-SHA256.
Assuming you got the RSASSA-PKCS1-SHA256 part right, you probably want to use VerifyHash and DigestValueBytes instead of VerifyData and x509DataBytes. (You also want to use cert.GetRSAPublicKey() instead of cert.PublicKey.Key)
byte[] SignatureValueBytes = Convert.FromBase64String(Signature.SignatureValue);
byte[] x509DataBytes = Convert.FromBase64String(Signature.x509Data);
byte[] DigestValueBytes = Convert.FromBase64String(Signature.DigestValue);
X509Certificate2 cert = new X509Certificate2(x509DataBytes);
using (RSA RSA = cert.GetRSAPublicKey())
{
bool a = RSA.VerifyHash(DigestValueBytes, SignatureValueBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Related
I want to digitally sign pdf documents using iText 7. The signature is created by an external service which returns a PKCS1 signature only. I then have to create and apply the PKCS7.
There is a good documentation for this scenario from iText: https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
Sample application
I have created a sample application which signs pdf documents via local certificate. This sample application can be cloned from https://github.com/suntsu42/PdfSignSamplePkcs1. In this sample application are two different ways of creating the PKCS7. Once manually and once via a IExternalSignature(PrivateKeySignature) implementation.
For both cases, the pdf digest which must be signed is created in the same way. The only difference is the way the PKCS7 is created.
The project on github (https://github.com/suntsu42/PdfSignSamplePkcs1) is complete and self contained. In the resources folder is a private key file (pfx) used for creating the signature as well as the root certificate. In order to run the example, it should be enough to just change the value of the resourcePath variable to accommodate your local system.
The signature creation can be toggled by changeing the value of createSignatureViaPlainPkcs1
using iText.Kernel.Pdf;
using iText.Signatures;
using System;
using System.IO;
namespace PdfSignSamplePkcs1
{
class Program
{
static void Main(string[] args)
{
// TODO >> Change this path based on your local system
var resourcePath = #"c:\project\github\PdfSignSamplePkcs1\Resources\";
var pdfToSignPath = Path.Combine(resourcePath, "test.pdf");
var signedPdfPath = Path.Combine(resourcePath, "signedPdf.pdf");
var privateKey = Path.Combine(resourcePath, "SignTest.pfx"); // not critical, self signed certificate
var privateKeyPassword = "test";
// ############
// Change value in order to create the PKCS7
// either manually or via Itext
// ############
bool createSignatureViaPlainPkcs1 = false;
//delete signed file if it exists
if (System.IO.File.Exists(signedPdfPath))
System.IO.File.Delete(signedPdfPath);
var pdfToSign = System.IO.File.ReadAllBytes(pdfToSignPath);
byte[] pdfDigest = null;
//#1 Prepare pdf for signing
var SignatureAttributeName = $"SignatureAttributeName_{DateTime.Now:yyyyMMddTHHmmss}";
byte[] preparedToSignPdf = null;
using (MemoryStream input = new MemoryStream(pdfToSign))
{
using (var reader = new PdfReader(input))
{
StampingProperties sp = new StampingProperties();
sp.UseAppendMode();
using (MemoryStream baos = new MemoryStream())
{
var signer = new PdfSigner(reader, baos, sp);
signer.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
signer.SetFieldName(SignatureAttributeName);
DigestCalcBlankSigner external = new DigestCalcBlankSigner(PdfName.Adobe_PPKLite, PdfName.Adbe_pkcs7_detached);
signer.SignExternalContainer(external, 32000);
//get digest to be signed
pdfDigest = external.PdfDigest;
preparedToSignPdf = baos.ToArray();
}
}
}
//#2 Create PKCS7
SignService ss = new SignService(pdfDigest, privateKey, privateKeyPassword);
byte[] signatureAsPkcs7 = null;
if (createSignatureViaPlainPkcs1)
signatureAsPkcs7 = ss.CreatePKCS7ViaPkcs1(); // >> Creates invalid pdf signature
else
signatureAsPkcs7 = ss.CreatePKCS7(); // Creates valid pdf signature
//#3 apply cms(PKCS7) to prepared pdf
ReadySignatureSigner extSigContainer = new ReadySignatureSigner(signatureAsPkcs7);
using (MemoryStream preparedPdfStream = new MemoryStream(preparedToSignPdf))
{
using (var pdfReader = new PdfReader(preparedPdfStream))
{
using (PdfDocument docToSign = new PdfDocument(pdfReader))
{
using (MemoryStream outStream = new MemoryStream())
{
PdfSigner.SignDeferred(docToSign, SignatureAttributeName, outStream, extSigContainer);
System.IO.File.WriteAllBytes(signedPdfPath, outStream.ToArray());
}
}
}
}
}
}
}
Manual creation of the pkcs7 signature
In this sample, first create a PKCS1 signature using a local certificate. The created PKCS1 signature then is applied to the PdfPKCS7 container via SetExternalDigest
The pdf created in this way is invalid.
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
Create PKCS7 signature using PrivateKeySignature implementation
In this sample, the PKCS7 is created using iText PrivateKeySignature. The signature is created with the same digest and the same private key as in the other example.
The pdf created here is valid. But since this approach doesn't allow the use of an external service for creating the signature, i cannot use it.
public byte[] CreatePKCS7ViaPkcs1()
{
//Load the certificate used for signing
signCertificatePrivateKey = LoadCertificateFromFile();
// create sha256 message digest
// This is from https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7
// Not sure if this is required, but the created signature is invalid either way
using (SHA256 sha256 = SHA256.Create())
{
Digest = sha256.ComputeHash(Digest);
}
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
Org.BouncyCastle.X509.X509Certificate cert = DotNetUtilities.FromX509Certificate(signCertificatePrivateKey);
PdfPKCS7 sgn = new PdfPKCS7(null, new[] { cert }, "SHA256", false);
sgn.SetExternalDigest(signature, null, "RSA");
//Return the complete PKCS7 CMS
return sgn.GetEncodedPKCS7(Digest, PdfSigner.CryptoStandard.CMS, null, null, null);
}
Remark (edit)
I think the reason for the problem is that i don't use GetAuthenticatedAttributeBytes for getting the hash to be signed. But i cannot use this method. The timestamp, ocsp and CLR are returned as part of the service call. Since the parameters for GetAuthenticatedAttributeBytes must be the same as when the signature is applied, i guess i cannot use this functionality.
Question
What is the reason the signature created via RSA is invalid in the resulting pdf?
edit: To be more specific: How can i create a valid pkcs7 container when the signature service returns PKCS1, Timestamp, Ocsp and CRL. What exactly must be signed in this case?
One error is fairly obvious:
In CreatePKCS7 you sign the to-be-signed attributes of the signature container (GetAuthenticatedAttributeBytes) which contain the document digest (Digest):
var sh = sgn.GetAuthenticatedAttributeBytes(Digest, PdfSigner.CryptoStandard.CMS, null, null);
byte[] extSignature = signature.Sign(sh);
In CreatePKCS7ViaPkcs1 you sign the document digest (Digest) itself:
//Create pkcs1 signature using RSA
byte[] signature = null;
using (var key = signCertificatePrivateKey.GetRSAPrivateKey())
{
signature = key.SignData(Digest, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
In both cases you continue by injecting the returned signature into a PdfPKCS7:
sgn.SetExternalDigest(extSignature, null, signature.GetEncryptionAlgorithm());
and
sgn.SetExternalDigest(signature, null, "RSA");
respectively.
That the first variant works, is an indicator telling you that SetExternalDigest expects as first parameter the signature (externally signed digest) of the to-be-signed attributes, not of the document digest directly.
Thus, in CreatePKCS7ViaPkcs1 you simply sign the wrong bytes!
You can fix CreatePKCS7ViaPkcs1 by (just like in CreatePKCS7) creating the PdfPKCS7 instance before and receiving the to-be-signed attributes from it (using GetAuthenticatedAttributeBytes). You may or may not have to hash the result hereof before signing - I'm not so well-versed in .NET crypto APIs.
I'm currently attempting to build a system which encrypts customer data and adds it to a remote MSMQ queue which is on another server. The data is then picked up by a job that runs every X Minute which will attempt to decrypt the data and processes it.
There is a requirement of us having to use .PFX Certificate to do the encryption/decryption (which I am aware is not be most efficient way of doing things but the requirement is there and I am unable to get this changed).
I am currently using a self-signed certificate using Open-SSL using:
openssl req -newkey rsa:2048 -nodes -keyout key.pem -x509 -days 365 -out certificate.pem
openssl pkcs12 -inkey key.pem -in certificate.pem -export -out certificate.p12
I have been successful at encrypting the data but every time I attempt a decrypt, I get the generic "The parameter is incorrect" exception.
To load the certificate, we have the .PFX file saved locally on the machines and using the X509Certificate2 class, we import it and use it to encrypt and decrypt. Here is a simplified version of the helper class that I am working with:
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, false);
return Convert.ToBase64String(encryptedBytes);
}
}
public static string DecryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.PrivateKey as RSACryptoServiceProvider)
{
var dataBytes = Convert.FromBase64String(data);
var decryptedBytes = rsa.Decrypt(dataBytes, false);
return Convert.ToBase64String(decryptedBytes);
}
}
private static X509Certificate2 GetCertificate()
{
var certificate = new X509Certificate2();
certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
return certificate;
}
The error always occurs on the "rsa.Decrypt()" call.
I have attempted the following:
Call "rsa.Decrypt()" in my "EncryptData" method right after encryption. This works with no issue and the "rsa.Decrypt" gives the me same bytes as the original data bytes.
Call "DecryptData" method straight after "EncryptData" call. The same issue occurs and I get Exception with "The parameter is incorrect"
This is why I suspect the fact a "new" X509Certificate2 is created that the private key is no longer the same and can no longer decrypt the data.
Please note that I am no security expert and have not worked with X509 Certificates or any cryptography for that matter so I am a bit out of my depth and may be doing something really silly so please let me know if I am.
Update 1 (08/03/2019)
Have updated the code code as per recommendation points 1-3 & 5 given by #bartonjs
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.GetRSAPublicKey())
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);
return Convert.ToBase64String(encryptedBytes);
}
}
public static string DecryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.GetRSAPrivateKey())
{
var dataBytes = Convert.FromBase64String(data);
var decryptedBytes = rsa.Decrypt(dataBytes, RSAEncryptionPadding.OaepSHA1);
return Convert.ToBase64String(decryptedBytes);
}
}
private static X509Certificate2 GetCertificate()
{
var certificate = new X509Certificate2("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
return certificate;
}
Added error message:
Message: The parameter is incorrect.
Stack Trace:
at System.Security.Cryptography.NCryptNative.DecryptData[T](SafeNCryptKeyHandle key, Byte[] data, T& paddingInfo, AsymmetricPaddingMode paddingMode, NCryptDecryptor`1 decryptor)
at System.Security.Cryptography.NCryptNative.DecryptDataOaep(SafeNCryptKeyHandle key, Byte[] data, String hashAlgorithm)
at System.Security.Cryptography.RSACng.Decrypt(Byte[] data, RSAEncryptionPadding padding)
I'm sorry to say that pretty much everything here is wrong.
GetCertificate
private static X509Certificate2 GetCertificate()
{
var certificate = new X509Certificate2();
certificate.Import("certificatePath", "certificatePassword", X509KeyStorageFlags.PersistKeySet);
return certificate;
}
You're importing with PersistKeySet, so you're slowly filling up your hard drive. See What is the rationale for all the different X509KeyStorageFlags?
Also, you're using certificate.Import, which is not available in .NET Core (because mutating X509Certificate2 objects is unexpected). Just use the constructor. So this whole method should be
private static X509Certificate2 GetCertificate()
{
// Assuming you do nothing else with the certificate than what's shown here,
// EphemeralKeySet will work for you (except on macOS).
return new X509Certificate2(path, password, X509KeyStorageFlags.EphemeralKeySet);
}
EncryptData
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (var rsa = certificate.PublicKey.Key as RSACryptoServiceProvider)
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, false);
return Convert.ToBase64String(encryptedBytes);
}
}
There are a couple of things wrong here.
1) PrivateKey is a shared property, so if it got read more than once you'd be disposing the object out from under another caller.
2) You're not disposing it if you happened to get a non-RSA certificate
3) You're using PrivateKey, which does not support the better RSA or DSA classes that support modern options.
4) You're the sole handler of the certificate, but didn't dispose it. Maybe your ownership semantics could be more clear.
From a security perspective, also 5) you're using PKCS#1 padding instead of OAEP
From a data perspective, also 6) why is encrypt being given base64 data instead of the raw data?
I won't address #s 4-6.
public static string EncryptData(string data)
{
var certificate = GetCertificate();
using (RSA rsa = certificate.GetRSAPublicKey())
{
var dataBytes = Convert.FromBase64String(data);
var encryptedBytes = rsa.Encrypt(dataBytes, RSAEncryptionPadding.Pkcs1);
return Convert.ToBase64String(encryptedBytes);
}
}
In this case it's correct to put it the private key in a using statement, the GetRSAPrivateKey() method always returns a new object.
DecryptData
Decrypt should be altered similarly to Encrypt.
If, after all of that, you're still getting exceptions, please include the exact message and the stack trace (at least the portions from the call to RSA.Decrypt through where it was thrown)
I am using the following code to successfully produce a signature for my SAML. I have the POST sorted using XML sign, however REDIRECT is completely different. I am unable to produce the same signature as https://www.samltool.com/sign_logout_req.php and when I try and validate a genuine signature it is failing.
I took it back to basics and tried to see if I could sign it in the same manner and I cannot which suggests something is wrong with the way I am forming the data.
The following details (into samltool.com):
<saml:LogoutRequest ID="_02380F63816E0E92D6537758C37FE05F" Version="2.0" IssueInstant="2017-06-21T15:34:59.911Z" Destination="https://myteststs.net/appname/auth/" xmlns:saml="urn:oasis:names:tc:SAML:2.0:protocol"><saml:Issuer xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">https://w.sp-app.com</saml:Issuer><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">1869374313</saml:NameID></saml:LogoutRequest>
Private key (testcert)
MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAL13oqZ+pNa/fYIc+GYBrENOQ9rKWMeQEL9iDJyj7DFrQA40jOCY1UiOT2uLYvIwfqTHMuPmmlOLSyqCumZbKYP6KIM0pe+vJcJO6Nxg81gmN3jx3GbnDsmhi54oAmj3JC/Z/WbliqUXjlIAUlzLmMll7/vy2V5ec/gxHBpuRWBjAgMBAAECgYAWiWn/+vV5k9qGhRKo8479jIw2tLX9uu27Dhso8LiyIitC5U5Skutfz4mz5acV3t3ZlNZBVJdL07hTrKwma7aSx1r6UwTtW002ZZzytEVn7G7ytOIXkT+q/TuooCR8aa88vwhUFPqCSOuZgOPH9ytqAkzDCaNgVKdhQgRgjxfOBQJBAOSu4t5AgFJUBOcYOEOm6+v8R1CqedfyOgya3g1gsA4VnuG+ms233ZxWSPkiMnoUpEh8gBnZyk6ZZSlk668rwBcCQQDUGYg7wVLqhPAyjfM74oaJgohyQfQK6rPnzlKoGbdDR0QRN545ATBsETi2GSIYAHgkgLDJw3/lw1wX1dXzFuWVAkABmR9IwlajPKcUHl02S9JWQdsVuztCwRSaxfJLUaOpVYlYtoZKbcCEuS2lYBHOPJqxTv1uMNFzHytP0L686KddAkART6gr4GKJG6KTLbzNhXafoJTMZo+pmHBomhFrAPZROm7WzOhQFMXD/D/ZtQFwXhFwQUSsoxU8Ro6sr1pQBe1lAkBlXndo3Bm6AITDDsJZYg10XiBMNj4743t0pV6jayf9UTRZHu2GI9AWoU3/FTQt34zbPz6TjlNuJnwMHwfCFk1F
x.509
MIICMTCCAZqgAwIBAgIQcuFBQn5d27JBvbkCO+utKjANBgkqhkiG9w0BAQUFADBXMVUwUwYDVQQDHkwAewAyAEYAOAA3ADkANQA4ADUALQA3AEMANQA0AC0ANAA1ADAARAAtADgAOABGAEIALQBBADMARgA3ADEAMwA2ADQANgBFAEMANgB9MB4XDTE2MDEwODExMTU0OFoXDTE3MDEwNzE3MTU0OFowVzFVMFMGA1UEAx5MAHsAMgBGADgANwA5ADUAOAA1AC0ANwBDADUANAAtADQANQAwAEQALQA4ADgARgBCAC0AQQAzAEYANwAxADMANgA0ADYARQBDADYAfTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvXeipn6k1r99ghz4ZgGsQ05D2spYx5AQv2IMnKPsMWtADjSM4JjVSI5Pa4ti8jB+pMcy4+aaU4tLKoK6Zlspg/oogzSl768lwk7o3GDzWCY3ePHcZucOyaGLnigCaPckL9n9ZuWKpReOUgBSXMuYyWXv+/LZXl5z+DEcGm5FYGMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBDXaccjXBrBhxp1fcEDm7MotKvgh8DxQAACk/Uxb4r2R6+LcePUxQTcxWVmyCQO0NR017FRf/fLFHmM9HZI3lwx5ka4xBnSOu8mejQ0KOYt4yf2VQG6pWGa046Ntip+KB/yDQKXQ3RHprsshe33MFlEWpDJyo6jyDpDUqLjPBvtg==
RelayState:
RELAYTEST
SigAlg:
#rsa-sha1
So... using the ssotool it produces the signature :
IG4VDmVwQRZWa75NmwjtqKlPVdCx6tm73gL7j3xvrqXsfirunUtr626SBmQJ4mke77bYzXg8D1hAy5EREOhz2QH23j47XexqbVSNTtAkZV7KP1/lO8K01tiQr8SGJqzdFor/FZZscIDFlw3cBLXhGSwWK9i0qO/e55qkgxJS9OA=
However.. using the code below (and many.. many.. variations) I cannot get it to produce that same signature. Please note the samlrequest is base64encoded with compression as recommended (but you can deflate it to the same output). I have followed this specification too (3.4.4.1):https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf
static byte[] Sign(string data, string certSubject)
{
// Access Personal (MY) certificate store of current user
X509Store my = new X509Store(StoreName.My, StoreLocation.LocalMachine);
my.Open(OpenFlags.ReadOnly);
// Find the certificate we’ll use to sign
RSACryptoServiceProvider csp = null;
foreach (X509Certificate2 cert in my.Certificates)
{
if (cert.Subject.Contains(certSubject))
{
// Get its associated CSP and private key
csp = (RSACryptoServiceProvider)cert.PrivateKey;
}
}
if (csp == null)
{
throw new Exception("No valid cert was found");
}
string certAlgorithm = csp.SignatureAlgorithm;
// Hash the data
SHA1Managed sha1 = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] dataRaw = encoding.GetBytes(data);
byte[] hash = sha1.ComputeHash(dataRaw);
// Sign the hash
return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}
static bool Verify(string text, byte[] signature, string certPublic)
{
// Load the certificate we’ll use to verify the signature from a file
X509Certificate2 appSigningX509Certificate = null;
var appSigningCertificateBytes = Convert.FromBase64String(certPublic);
appSigningX509Certificate = new X509Certificate2(appSigningCertificateBytes);
// Get its associated CSP and public key
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)appSigningX509Certificate.PublicKey.Key;
// Hash the data
SHA1Managed sha1 = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1.ComputeHash(data);
// Verify the signature with the hash
return csp.VerifyHash(hash, CryptoConfig.MapNameToOID("SHA1"), signature);
}
static void Main(string[] args)
{
// Usage sample
try
{
string cert = "MIICMTCCAZqgAwIBAgIQcuFBQn5d27JBvbkCO+utKjANBgkqhkiG9w0BAQUFADBXMVUwUwYDVQQDHkwAewAyAEYAOAA3ADkANQA4ADUALQA3AEMANQA0AC0ANAA1ADAARAAtADgAOABGAEIALQBBADMARgA3ADEAMwA2ADQANgBFAEMANgB9MB4XDTE2MDEwODExMTU0OFoXDTE3MDEwNzE3MTU0OFowVzFVMFMGA1UEAx5MAHsAMgBGADgANwA5ADUAOAA1AC0ANwBDADUANAAtADQANQAwAEQALQA4ADgARgBCAC0AQQAzAEYANwAxADMANgA0ADYARQBDADYAfTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvXeipn6k1r99ghz4ZgGsQ05D2spYx5AQv2IMnKPsMWtADjSM4JjVSI5Pa4ti8jB+pMcy4+aaU4tLKoK6Zlspg/oogzSl768lwk7o3GDzWCY3ePHcZucOyaGLnigCaPckL9n9ZuWKpReOUgBSXMuYyWXv+/LZXl5z+DEcGm5FYGMCAwEAATANBgkqhkiG9w0BAQUFAAOBgQBDXaccjXBrBhxp1fcEDm7MotKvgh8DxQAACk/Uxb4r2R6+LcePUxQTcxWVmyCQO0NR017FRf/fLFHmM9HZI3lwx5ka4xBnSOu8mejQ0KOYt4yf2VQG6pWGa046Ntip+KB/yDQKXQ3RHprsshe33MFlEWpDJyo6jyDpDUqLjPBvtg==";
string samlRequestCompressed = "nZFPS8QwEMW/Ssl906TZ/gttQWwLhdWDyh68SKjBLbRJ7ExQv71traA38TiPee/9hilATaM82Rfr8U6/eg0YdHVJnlgkMtYmIuNJw5o8qpNYpGmcXYu0bVjckuCsZxisKUlEGQk6AK87A6gMLhLj6YElh4g/8FiKo4xzmnP+SIJ6aRiMws15QXQgwxC1Uab/MGrSdFBTP1r/TI3GUDm3iqE7KI+XkATv02hArtAl8bORVsEAct0Bib28v7o5yYVHutmi7e1IqmK7cMOb/2xXAHpeGUn1zfhGYaFwjvZ2KsIfoXvD7RLS1f9p4FmSi/QouNhzv6Kqffr1nOoT";
string relaystate = "RELAYTEST";
string algorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
string data = String.Empty;
if (String.IsNullOrEmpty(relaystate))
{
data = String.Format("SAMLRequest={0}&SigAlg={1}", HttpUtility.UrlEncode(samlRequestCompressed), HttpUtility.UrlEncode(algorithm));
}
else
{
data = String.Format("SAMLRequest={0}&RelayState={1}&SigAlg={2}", HttpUtility.UrlEncode(samlRequestCompressed,Encoding.UTF8), HttpUtility.UrlEncode(relaystate,Encoding.UTF8), HttpUtility.UrlEncode(algorithm,Encoding.UTF8));
}
// Sign text
byte[] signature = Sign(data, "{2F879585-7C54-450D-88FB-A3F713646EC6}");
string b64encodedSig = Convert.ToBase64String(signature);
string expectedSig = "IG4VDmVwQRZWa75NmwjtqKlPVdCx6tm73gL7j3xvrqXsfirunUtr626SBmQJ4mke77bYzXg8D1hAy5EREOhz2QH23j47XexqbVSNTtAkZV7KP1/lO8K01tiQr8SGJqzdFor/FZZscIDFlw3cBLXhGSwWK9i0qO/e55qkgxJS9OA=";
if (b64encodedSig != expectedSig)
{
Console.WriteLine("Not what i expected");
Environment.Exit(0);
}
// Verify signature. Testcert.cer corresponds to “cn=my cert subject”
if (Verify(data, signature, cert))
{
Console.WriteLine("Signature verified");
}
else
{
Console.WriteLine("ERROR: Signature not valid!");
}
}
catch (Exception ex)
{
Console.WriteLine("EXCEPTION: " +ex.Message);
}
Console.ReadKey();
}
I cannot seem to understand how to produce the sign data in the same way. I have also confirmed the locally installed certificate is exactly the same as the one above.
I have resolved this now.
The solution was that the HttpUtility.URLEncode is not encoding it to the same standard as the SAML standards (or OneLogin). I figured it out by looking at the compressed data and that matched but the URL Encoded one different.
The answer was to use Uri.EscapeString.
In order to get the same signature as OneLogin, you have to use the same URL encoding as them. Other URL encodings will result in different signatures, but they are perfectly valid as well.
See the SAML specification (3.4.4.1):
Further, note that URL-encoding is not canonical; that is, there are multiple legal encodings for a given value. The relying party MUST therefore perform the verification step using the original URL-encoded values it received on the query string. It is not sufficient to re-encode the parameters after they have been processed by software because the resulting encoding may not match the signer's encoding.
As noted in another answer to this question, OneLogin seems to use URL encoding matching System.Net.WebUtility.UrlEncode() in .NET -- but note also that they do not URL encode the base64-encoded signature itself, even though it can contain characters such as [+/=]. The SAML specification is not very clear, but seems to suggest that this is wrong,
Note that some characters in the base64-encoded signature value may themselves require URL-encoding before being added.
This last bit is crucial to be able to validate your signature with their tools.
My application will take a set of files and sign them. (I'm not trying to sign an assembly.) There is a .p12 file that I get the private key from.
This is the code I was trying to use, but I get a System.Security.Cryptography.CryptographicException "Invalid algorithm specified.".
X509Certificate pXCert = new X509Certificate2(#"keyStore.p12", "password");
RSACryptoServiceProvider csp = (RSACryptoServiceProvider)pXCert.PrivateKey;
string id = CryptoConfig.MapNameToOID("SHA256");
return csp.SignData(File.ReadAllBytes(filePath), id);
According to this answer it can't be done (the RSACryptoServiceProvider does not support SHA-256), but I was hoping that it might be possible using a different library, like Bouncy Castle.
I'm new to this stuff and I'm finding Bouncy Castle to be very confusing. I'm porting a Java app to C# and I have to use the same type of encryption to sign the files, so I am stuck with RSA + SHA256.
How can I do this using Bouncy Castle, OpenSSL.NET, Security.Cryptography, or another 3rd party library I haven't heard of? I'm assuming, if it can be done in Java then it can be done in C#.
UPDATE:
this is what I got from the link in poupou's anwser
X509Certificate2 cert = new X509Certificate2(KeyStoreFile, password");
RSACryptoServiceProvider rsacsp = (RSACryptoServiceProvider)cert.PrivateKey;
CspParameters cspParam = new CspParameters();
cspParam.KeyContainerName = rsacsp.CspKeyContainerInfo.KeyContainerName;
cspParam.KeyNumber = rsacsp.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2;
RSACryptoServiceProvider aescsp = new RSACryptoServiceProvider(cspParam);
aescsp.PersistKeyInCsp = false;
byte[] signed = aescsp.SignData(File.ReadAllBytes(file), "SHA256");
bool isValid = aescsp.VerifyData(File.ReadAllBytes(file), "SHA256", signed);
The problem is that I'm not getting the same results as I got with the original tool. As far as I can tell from reading the code the CryptoServiceProvider that does the actual signing is not using the PrivateKey from key store file. Is that Correct?
RSA + SHA256 can and will work...
Your later example may not work all the time, it should use the hash algorithm's OID, rather than it's name. As per your first example, this is obtained from a call to CryptoConfig.MapNameToOID(AlgorithmName) where AlgorithmName is what you are providing (i.e. "SHA256").
First you are going to need is the certificate with the private key. I normally read mine from the LocalMachine or CurrentUser store by using a public key file (.cer) to identify the private key, and then enumerate the certificates and match on the hash...
X509Certificate2 publicCert = new X509Certificate2(#"C:\mycertificate.cer");
//Fetch private key from the local machine store
X509Certificate2 privateCert = null;
X509Store store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
foreach( X509Certificate2 cert in store.Certificates)
{
if (cert.GetCertHashString() == publicCert.GetCertHashString())
privateCert = cert;
}
However you get there, once you've obtained a certificate with a private key we need to reconstruct it. This may be required due to the way the certificate creates it's private key, but I'm not really sure why. Anyway, we do this by first exporting the key and then re-importing it using whatever intermediate format you like, the easiest is xml:
//Round-trip the key to XML and back, there might be a better way but this works
RSACryptoServiceProvider key = new RSACryptoServiceProvider();
key.FromXmlString(privateCert.PrivateKey.ToXmlString(true));
Once that is done we can now sign a piece of data as follows:
//Create some data to sign
byte[] data = new byte[1024];
//Sign the data
byte[] sig = key.SignData(data, CryptoConfig.MapNameToOID("SHA256"));
Lastly, the verification can be done directly with the certificate's public key without need for the reconstruction as we did with the private key:
key = (RSACryptoServiceProvider)publicCert.PublicKey.Key;
if (!key.VerifyData(data, CryptoConfig.MapNameToOID("SHA256"), sig))
throw new CryptographicException();
The use of privateKey.toXMLString(true) or privateKey.exportParameters(true) aren't usable in a secure environment, since they require your private key to be exportable, which is NOT a good practice.
A better solution is to explicitly load the "Enhanced" crypto provider as such:
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName);
privKey = new RSACryptoServiceProvider(cspparams);
This is how I dealt with that problem:
X509Certificate2 privateCert = new X509Certificate2("certificate.pfx", password, X509KeyStorageFlags.Exportable);
// This instance can not sign and verify with SHA256:
RSACryptoServiceProvider privateKey = (RSACryptoServiceProvider)privateCert.PrivateKey;
// This one can:
RSACryptoServiceProvider privateKey1 = new RSACryptoServiceProvider();
privateKey1.ImportParameters(privateKey.ExportParameters(true));
byte[] data = Encoding.UTF8.GetBytes("Data to be signed");
byte[] signature = privateKey1.SignData(data, "SHA256");
bool isValid = privateKey1.VerifyData(data, "SHA256", signature);
I settled on changing the key file to specify the appropriate Crypto Service Provider, avoiding the issue in .NET altogether.
So when I create a PFX file out of a PEM private key and a CRT public certificate, I do it as follows:
openssl pkcs12 -export -aes256 -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider" -inkey priv.pem -in pub.crt -out priv.pfx
The key part being -CSP "Microsoft Enhanced RSA and AES Cryptographic Provider".
(-inkey specifies the private key file and -in specifies the public certificate to incorporate.)
You may need to tweak this for the file formats you have on hand. The command line examples on this page can help with that:
https://www.sslshopper.com/ssl-converter.html
I found this solution here:
http://hintdesk.com/c-how-to-fix-invalid-algorithm-specified-when-signing-with-sha256/
Use can use this on more recent frameworks.
public byte[] GetSignature(byte[] inputData)
{
using (var rsa = this.signingCertificate.GetRSAPrivateKey())
{
return rsa.SignData(inputData, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
public bool ValidateSignature(byte[] inputData, byte[] signature)
{
using (var rsa = this.signingCertificate.GetRSAPublicKey())
{
return rsa.VerifyData(inputData, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
The signingCertificate above is a X509Certificate2 with a private key. This method does not require you to import any existing keys and works in a secure environment.
When you use a certificate to get your RSACryptoServiceProvider it really matters what's the underlying CryptoAPI provider. By default, when you create a certificate with 'makecert', it's "RSA-FULL" which only supports SHA1 hashes for signature. You need the new "RSA-AES" one that supports SHA2.
So, you can create your certificate with an additional option: -sp "Microsoft Enhanced RSA and AES Cryptographic Provider" (or an equivalent -sy 24) and then your code would work without the key juggling stuff.
Here is how I signed a string without having to modify the certificate (to a Microsoft Enhanced RSA and AES Cryptographic provider).
byte[] certificate = File.ReadAllBytes(#"C:\Users\AwesomeUser\Desktop\Test\ServerCertificate.pfx");
X509Certificate2 cert2 = new X509Certificate2(certificate, string.Empty, X509KeyStorageFlags.Exportable);
string stringToBeSigned = "This is a string to be signed";
SHA256Managed shHash = new SHA256Managed();
byte[] computedHash = shHash.ComputeHash(Encoding.Default.GetBytes(stringToBeSigned));
var certifiedRSACryptoServiceProvider = cert2.PrivateKey as RSACryptoServiceProvider;
RSACryptoServiceProvider defaultRSACryptoServiceProvider = new RSACryptoServiceProvider();
defaultRSACryptoServiceProvider.ImportParameters(certifiedRSACryptoServiceProvider.ExportParameters(true));
byte[] signedHashValue = defaultRSACryptoServiceProvider.SignData(computedHash, "SHA256");
string signature = Convert.ToBase64String(signedHashValue);
Console.WriteLine("Signature : {0}", signature);
RSACryptoServiceProvider publicCertifiedRSACryptoServiceProvider = cert2.PublicKey.Key as RSACryptoServiceProvider;
bool verify = publicCertifiedRSACryptoServiceProvider.VerifyData(computedHash, "SHA256", signedHashValue);
Console.WriteLine("Verification result : {0}", verify);
According to this blog it should work with FX 3.5 (see note below). However it's important to recall that most of .NET cryptography is based on CryptoAPI (even if CNG is being more and more exposed in recent FX releases).
The key point is that CryptoAPI algorithm support depends on the Crypto Service Provider (CSP) being used and that varies a bit between Windows versions (i.e. what's working on Windows 7 might not work on Windows 2000).
Read the comments (from the blog entry) to see a possible workaround where you specify the AES CSP (instead of the default one) when creating your RSACCryptoServiceProvider instance. That seems to work for some people, YMMV.
Note: this is confusing to many people because all the released .NET frameworks includes a managed implementation of SHA256 which cannot be used by CryptoAPI. FWIW Mono does not suffer from such issues ;-)
I know this is an old thread but for those still stuck in the past and looking for an answer, the following worked for me based off #BKibler's answer. The comments stated it's not using the correct key and it's because the solution is missing a couple key settings.
// Find my openssl-generated cert from the registry
var store = new X509Store(StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadOnly);
var certificates = store.Certificates.Find(X509FindType.FindBySubjectName, "myapp.com", true);
var certificate = certificates[0];
store.Close();
// Note that this will return a Basic crypto provider, with only SHA-1 support
var privKey = (RSACryptoServiceProvider)certificate.PrivateKey;
// Force use of the Enhanced RSA and AES Cryptographic Provider with openssl-generated SHA256 keys
var enhCsp = new RSACryptoServiceProvider().CspKeyContainerInfo;
if (!Enum.TryParse<KeyNumber>(privKey.CspKeyContainerInfo.KeyNumber.ToString(), out var keyNumber))
throw new Exception($"Unknown key number {privKey.CspKeyContainerInfo.KeyNumber}");
var cspparams = new CspParameters(enhCsp.ProviderType, enhCsp.ProviderName, privKey.CspKeyContainerInfo.KeyContainerName)
{
KeyNumber = (int)keyNumber,
Flags = CspProviderFlags.UseExistingKey
};
privKey = new RSACryptoServiceProvider(cspparams);
You need to set both "KeyNumber" and "Flags" so the existing (non-exportable) key is used and you can use the public key from the certificate to verify.
I have noticed similar issues in .NET with the wrong private key being used (or was it flat-out errors? I do not recall) when the certificate I am working with is not in the user/computer certificate store. Installing it into the stored fixed the problem for my scenario and things started working as expected - perhaps you can try that.
I'm looking to convert some C# code to the equivalent in Java.
The C# code takes some string content, and a signature (generated using the private key, on a seperate machine) and combined with the public key it verifies the signature matches, providing a level of assurance that the request has not been tampered with.
public bool VerifySignature(string content, byte[] signatureBytes, AsymmetricAlgorithm publicKey)
{
var hash = new MD5CryptoServiceProvider();
byte[] dataBuffer = Encoding.ASCII.GetBytes(content);
var cs = new CryptoStream(Stream.Null, hash, CryptoStreamMode.Write);
cs.Write(dataBuffer, 0, dataBuffer.Length);
cs.Close();
var deformatter = new RSAPKCS1SignatureDeformatter(publicKey);
deformatter.SetHashAlgorithm("MD5");
return deformatter.VerifySignature(hash, signatureBytes);
}
The public key itself is an X509 Certificate - constructed from a .cer file, stored as assembly resource i.e.
byte[] data; // data is read from a resource stream.
var publicKey = new X509Certificate2(data, "", X509KeyStorageFlags.MachineKeySet).PublicKey.Key
What I'm looking to do is emulate this functionality in Java, so I can verify the signature generated by some code in C#... I've started investigating the crypto functionality of Java, but I'm a bit of a java noob. Here's what I've come up with so far:
byte[] certContents=null;
byte[] signature=null;
String contents = "abc";
// load cert
CertificateFactory factory = CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate) factory.generateCertificate(new ByteArrayInputStream(certContents));
// grab public key
RSAPublicKey publicKey = (RSAPublicKey)cert.getPublicKey();
// get sha1 hash for contents
Mac mac = Mac.getInstance("HmacSHA1");
mac.update(contents.getBytes());
byte[] hash = mac.doFinal();
// get cipher
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, publicKey);
// verify signature of contents matches signature passed to method somehow (and this is where I'm stuck)
Can anyone provide any insight into how I can verify the signature - or provide links to some resources which might explain the java.crypto and java.security.cert usage better then the run of the mill java docs.
That C# code looks really confusing to me. It use SHA1CryptoServiceProvider but uses MD5 hash so I can't tell which hashing algorithm it's using. I assume it's MD5.
The signature verification process involves padding so your code wouldn't work. Following is some snippet from my code and you can use it to verify the signature. data is the bytes to sign and sigBytes holds the signature.
String algorithm = "MD5withRSA";
// Initialize JCE provider
Signature verifier = Signature.getInstance(algorithm);
// Do the verification
boolean result=false;
try {
verifier.initVerify(cert); // This one checks key usage in the cert
verifier.update(data);
result = verifier.verify(sigBytes);
}
catch (Exception e) {
throw new VerificationException("Verification error: "+e, e);
}