iText SignExternalContainer and altered or corrupted issue - c#

I am trying to sign a pdf using a remote web service which returns PKCS#7 signature. I sent calculated file hash which need to signing, and service return Base64Encoded PKCS#7 certificate.
I read some cases like me (link1 link2). After that this implementation should meet my flow.
Signing Method
public static void sign(string unsignedPdf, string signedPdf)
{
PdfReader reader = new PdfReader(unsignedPdf);
FileStream os = new FileStream(signedPdf, FileMode.Create);
PdfStamper stamper = PdfStamper.CreateSignature(reader, os, '\0');
// Creating the appearance
PdfSignatureAppearance appearance = stamper.SignatureAppearance;
appearance.Reason = "For a reason surely";
appearance.Location = "Positively somewhere";
appearance.SetVisibleSignature(new Rectangle(36, 748, 144, 780), 1, "sig");
IExternalSignatureContainer externalSignatureContainer = new ExternalServiceContainerSigner();
// Creating the signature
MakeSignature.SignExternalContainer(appearance, externalSignatureContainer, 8192);
}
ExternalSignatureContainer implementation
class ExternalServiceContainerSigner : IExternalSignatureContainer
{
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.Put(PdfName.FILTER, PdfName.ADOBE_PPKLITE);
signDic.Put(PdfName.SUBFILTER, PdfName.ADBE_PKCS7_DETACHED);
}
public byte[] Sign(Stream data)
{
String hashAlgorithm = "SHA256";
byte[] hash = DigestAlgorithms.Digest(data, hashAlgorithm);
var transId = Program.GetFixedLengthStrinng(8);
var transDate = XmlConvert.ToString(DateTime.Now);
string pkcsBase64 = MobileSignClient.getSignatureAsync(null, null, transId, transDate, calculatedHash: Convert.ToBase64String(hash)).Result;
var retVar = Convert.FromBase64String(pkcsBase64);
return retVar;
}
}
Service response for "JQNPS3pCdY/4Ks7afVVGwB8iZUdZU3ClSScA6YYHpUg=" hash:
gist
Original File:
https://drive.google.com/file/d/19YgUTy9rL8tLZKU_EdRmkBJHvor1zmTw/view?usp=sharing
Signed File:
https://drive.google.com/file/d/1vQnBKdpOlOnQtDyveC0017TXh90ZVHfR/view?usp=sharing
After signing, signature looks invalid.
What I am missing?

First the good news: The base64 encoded hash you mention, JQNPS3pCdY/4Ks7afVVGwB8iZUdZU3ClSScA6YYHpUg= is the correct SHA256 hash value of the signed byte ranges of the PDF. Thus, the error must be in these few lines that request a CMS signature for that hash value:
var transId = Program.GetFixedLengthStrinng(8);
var transDate = XmlConvert.ToString(DateTime.Now);
string pkcsBase64 = MobileSignClient.getSignatureAsync(null, null, transId, transDate, calculatedHash: Convert.ToBase64String(hash)).Result;
var retVar = Convert.FromBase64String(pkcsBase64);
Then the bad news: The CMS signature container returned by these lines is wrong in many ways:
The signature container contains encapsulated content:
SEQUENCE (2 elem)
OBJECT IDENTIFIER 1.2.840.113549.1.7.1 data (PKCS #7)
[0] (1 elem)
OCTET STRING (44 byte) JQNPS3pCdY/4Ks7afVVGwB8iZUdZU3ClSScA6YYHpUg=
This is incorrect, No data shall be encapsulated for embedded PDF signatures of type adbe.pkcs7.detached (ISO 32000-2:2020 section 12.8.3.3).
Possible solution: The signing API should offer a flag to request signatures without encapsulating any signed content.
The messageDigest signed attribute contains the SHA1 hash of the encapsulated content.
SEQUENCE (2 elem)
OBJECT IDENTIFIER 1.2.840.113549.1.9.4 messageDigest (PKCS #9)
SET (1 elem)
OCTET STRING (20 byte) 03AC720F1BE912035E483BF07486319D07631B35
This is understandable given that there wrongly is encapsulated content, see previous item. What should be here, though, is the hash value you provided, not another hash of it.
Possible solution: In addition to the flag above, the signing API should offer a flag to indicate that the value you send is not the plain data to sign but already the hash thereof.
By the way, it is extremely weird that SHA1 is used here as the digest algorithm mentioned in the signature is SHA256...
The digest in the encrypted signature value is your provided hash value.
Given the presence of encapsulated data and signed attributes, this is completely wrong. It should have been the digest of the combined encapsulated content and signed attributes or at least the digest of the signed attributes but not the digest value you provided.
Thus, the next thing you should do is study the parameters of your MobileSignClient (using the documentation and/or trial&error) and find a signing call that returns a CMS signature container with the required contents.

Related

verify non detached PKCS signature

Dears
Please help me to understand non detached signature validation process.
I've used sample code (slightly modified) from SignedCms.ComputeSignature and CheckSignature documentation.
Here it is
[TestMethod]
public void CheckSignaturePlain()
{
var x509 = new X509Helper(LogManager.GetLogger("x509"));
X509Certificate2 certificate = x509.GetCertificate("XML_SIGN_TEST_CERT");
var str = "quick brown fox jumps over lazy dog";
var data = Encoding.ASCII.GetBytes(str);
detached = false; // tests are passed for detached signatures only
using (var sha256 = new SHA256CryptoServiceProvider()) // One thing though, Microsoft recommends that you avoid using any managed classes. You should change SHA256Managed to SHA256CryptoServiceProvider. https://blogs.msdn.microsoft.com/winsdk/2015/11/14/using-sha256-with-the-signedxml-class/
{
var hash = sha256.ComputeHash(data);
var signature = Sign(hash, certificate);
SignCheck(hash, signature);
try
{
data = data.Skip(1).ToArray(); // lets change the data to get broken signature
var hash2 = sha256.ComputeHash(data);
SignCheck(hash2, signature);
Assert.Fail("signature verification should fail");
}
catch (CryptographicException ce)
{
TestContext.WriteLine(ce.Message);
}
}
}
byte[] Sign(byte[] hash, X509Certificate2 cert)
{
ContentInfo contentInfo = new ContentInfo(hash);
SignedCms signedCms = new SignedCms(contentInfo, detached);
CmsSigner cmsSigner = new CmsSigner(cert);
cmsSigner.IncludeOption = X509IncludeOption.WholeChain;
signedCms.ComputeSignature(cmsSigner);
byte[] cmsMessage = signedCms.Encode();
return cmsMessage;
}
bool detached;
void SignCheck(byte[] hash, byte[] signature)
{
var contentInfo2 = new ContentInfo(hash);
var signedCms = new SignedCms(contentInfo2, detached);
signedCms.Decode(signature);
signedCms.CheckSignature(true);
}
However it works properly (from my point of view) only for detached signatures.
If detached = false is set then signature verification does not fail for changed data.
It happens because data signed were included to the signature object and SignedCms.CheckSignature ignores hash that was calculated from changed data.
Is it possible to use non-detached signature and get signedCms.CheckSignature to take into account hash that was calculated from changed data?
Or I should extract signed data from non-detached signature, calculate hash over data and compare them manually?
I'd like to use non-detached signature. Signed data from the signature (hash actually) should be used as message identifier on abstraction layer that has no idea how to calculate hash for different kind of objects.
In non-detached mode the content is within the payload, so during Decode the ContentInfo you provided is discarded and the one from within the payload is used instead.
if (!this.Detached) {
Oid contentType = PkcsUtils.GetContentType(m_safeCryptMsgHandle);
byte[] content = PkcsUtils.GetContent(m_safeCryptMsgHandle);
m_contentInfo = new ContentInfo(contentType, content);
}
(from SignedCms.Decode on referencesource)
In detached signature mode the SignedCms data represents a signature (and metadata) over some data you have to provide. Using standard cryptographic signature behaviors it takes the input data, hashes it, and performs a private key operation on the hash to generate the signature.
In embedded (non-detached) signature mode the SignedCms data is a single blob which contains the data as well as the signature (and metadata). So sending the CMS object in non-detached mode replaces sending your original data (because the original data is now embedded).
Unless the thing you want to transport is actually just the hash, you should feed SignedCms the actual data value, since it hashes it itself as part of computing the signature.

SAML Redirect sign or verify failing to produce correct signature

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.

Why is there a difference between Node.js' sign and CryptographicEngine sign results?

I am trying to verify a signed hash made by the Node.js Crypto API using UWP's CryptographicEngine. Because the Verify method kept returning false, I am now comparing both signature methods. When I sign a simple string using both systems, I get different results.
Here is the Crypto JS code:
//Generate signer and hasher
var signature = crypto.createSign('RSA-SHA256');
var hasher = crypto.createHash("SHA256");
hasher.update('mydata');
//Generate hash from data
hashresult = hasher.digest('base64');
signature.update(hashresult);
//Read private key
var inputkey = fs.readFileSync('private.pem');
//Sign Data
var result = signature.sign(inputkey, 'base64');
And here is the CryptographicEngine code:
IBuffer buffer = CryptographicBuffer.ConvertStringToBinary("mydata", BinaryStringEncoding.Utf8);
HashAlgorithmProvider hashAlgorithm = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha256);
IBuffer hashBuffer = hashAlgorithm.HashData(buffer);
var basehash = CryptographicBuffer.EncodeToBase64String(hashBuffer);
Debug.WriteLine("HASHED RESULT");
Debug.WriteLine(basehash);
//ENCRYPT SIGNATURE using GetPrivateKey to get base64 key without headers
string privatekey = await GetPrivateKey();
//Convert key to IBuffer
IBuffer privatekeybuf = CryptographicBuffer.DecodeFromBase64String(privatekey);
AsymmetricKeyAlgorithmProvider provider = AsymmetricKeyAlgorithmProvider.OpenAlgorithm(AsymmetricAlgorithmNames.RsaSignPkcs1Sha256);
CryptographicKey encryptKey = provider.ImportKeyPair(privatekeybuf, CryptographicPrivateKeyBlobType.Pkcs1RsaPrivateKey);
var encryptedresult = CryptographicEngine.Sign(encryptKey, hashbuffer);
string resultencrypted = CryptographicBuffer.EncodeToBase64String(encryptedresult);
Debug.WriteLine("ENCRYPTED RESULT");
Debug.WriteLine(resultencrypted);
I have verified that the two hashes that are created in both JS and UWP are equal. The result of both signing methods however, is not. How can these be different? It seems the encoding is equal. I have tried both Sign and SignHashedData in UWP and have also tried various other encodings.
Any ideas?
Both Node.js's Sign class and CryptographicEngine.Sign expect the unhashed data. You don't need to hash your data additionally. You should remove the double hashing in Node.js and C#.
RSA encryption and signature generation uses padding in order to provide any meaningful security. There are different types of padding which are equivalent, but some are randomized. If either Node.js or UWP uses a randomized padding, then you can't simply compare the signature results. The only way to check if your implementations are compatible, would be to sign some data in one end and verify in the other. Then repeat that in the other direction.

How to get digital signature value from x509 certificate in C#

Does anyone know how is it possible with C# to get digital signature value from x509 certificate (which is actually in x509Store and not to validate from file) and to show it for example in textbox. I know that GetRawCertDataString() returns the raw data for entire x509 certificate which includes digital signature on the last rows but I can not find a command which returns only digital signature.
Your best way is to get ASN.1 parser and extract digital signature, or do some p/invoke stuff. You will need to use CryptDecodeObject function and pass X509_CERT as lpszStructType parameter. The function returns (in the pvStructInfo) a pointer to a CERT_SIGNED_CONTENT_INFO structure. This structure has Signature field which is a simple BIT_BLOB structure with byte array in the cbData and pbData fields (use Marshal.Copy to copy bytes from unmanaged memory to managed byte array).
public static string Sign(this X509Certificate2 x509, string message)
{
byte[] data = Encoding.UTF8.GetBytes(message);
byte[] signedData;
using (MD5 hasher = MD5.Create())
{
RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)x509.PrivateKey;
signedData = rsa.SignData(data, hasher);
}
return Convert.ToBase64String(signedData);// +Environment.NewLine + Environment.NewLine;
//return ByteArrayToString(signedData); //Convert.ToBase64String(signedData);
}

Verifying a signature in java using a certificates public key

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);
}

Categories