I'm using iText 7 for applying signatures to pdf documents. I also use my own implementation of IExternalSignatureContainer in order to integrate the certificates into the PKCS7 CMS since the signing service only returns a PKCS1 signature.
The signature process is asynchrony (user has to authenticate) i'd like to do the following:
Prepare the document (PdfReader)
Get the hash value of the document back to the user
Throw the document away (PdfReader)
Let the user authenticate (Not directly related with the iText signing process) and create the signature (PKCS1)
If the user is authenticated, Prepare the document again and apply the signature.
Reason for this is that i don't have too keep the prepared document in memory and also for batch signing.
My problem is that the hash value created is always different. (Even if i set the date/time via pdfSigner.SetSignDate to the same value) or every PdfReader/PdfSigner instance.
//Create the hash of of the pdf document
//Part of my IExternalSignatureContainer Sign method
//Called from iText pdfSigner.SignExternalContainer
//The produced hash is always different
byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
Question: Is there a way to
Produce the hash of a pdf document "ahead of time" on one instance of the PdfReader
Create the signature
Apply the signature on a different instance of the PdfReader
Attached is a complete example of the process (Including the signature creation which actually needs to be done by a different service)
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using iText.Kernel.Pdf;
using iText.Signatures;
using Org.BouncyCastle.X509;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
namespace SignExternalTestManuel
{
class Program
{
const string filePath = #"c:\temp\pdfsign\";
public static string pdfToSign = Path.Combine(filePath, #"test.pdf");
public static string destinationFile = Path.Combine(filePath, "test_signed.pdf");
public static string LocalUserCertificatePublicKey = Path.Combine(filePath, "BITSignTestManuel5Base64.cer");
public static string LocalCaCertificatePublicKey = Path.Combine(filePath, "BITRoot5Base64.cer");
public static string privateKeyFile = Path.Combine(filePath, "BITSignTestManuel5.pfx");
public static string privateKeyPassword = "test";
public static void Main(String[] args)
{
PdfReader reader = new PdfReader(pdfToSign);
using (FileStream os = new FileStream(destinationFile, FileMode.OpenOrCreate))
{
StampingProperties stampingProperties = new StampingProperties();
stampingProperties.UseAppendMode();
PdfSigner pdfSigner = new PdfSigner(reader, os, stampingProperties);
pdfSigner.SetCertificationLevel(PdfSigner.NOT_CERTIFIED);
IExternalSignatureContainer external = new GsSignatureContainer(
PdfName.Adobe_PPKLite,
PdfName.Adbe_pkcs7_detached);
pdfSigner.SetSignDate(new DateTime(2021, 2, 22, 10, 0, 0));
pdfSigner.SetFieldName("MySignatureField");
pdfSigner.SignExternalContainer(external, 32000);
}
}
}
public class GsSignatureContainer : IExternalSignatureContainer
{
private PdfDictionary sigDic;
public GsSignatureContainer(PdfName filter, PdfName subFilter)
{
sigDic = new PdfDictionary();
sigDic.Put(PdfName.Filter, filter);
sigDic.Put(PdfName.SubFilter, subFilter);
}
/// <summary>
/// Implementation based on https://kb.itextpdf.com/home/it7kb/examples/how-to-use-a-digital-signing-service-dss-such-as-globalsign-with-itext-7#HowtouseaDigitalSigningService(DSS)suchasGlobalSign,withiText7-Examplecode
/// </summary>
/// <param name="pdfStream"></param>
/// <returns></returns>
public byte[] Sign(Stream pdfStream)
{
//Create the certificate chaing since the signature is just a PKCS1, the certificates must be added to the signature
X509Certificate[] chain = null;
string cert = System.IO.File.ReadAllText(Program.LocalUserCertificatePublicKey);
string ca = System.IO.File.ReadAllText(Program.LocalCaCertificatePublicKey);
chain = CreateChain(cert, ca);
X509CrlParser p = new X509CrlParser();
String hashAlgorithm = DigestAlgorithms.SHA256;
PdfPKCS7 pkcs7Signature = new PdfPKCS7(null, chain, hashAlgorithm, false);
//Create the hash of of the pdf document
//Part of my IExternalSignatureContainer Sign method
//Called from iText pdfSigner.SignExternalContainer
//The produced hash is always different
byte[] hash = DigestAlgorithms.Digest(pdfStream, DigestAlgorithms.GetMessageDigest(hashAlgorithm));
byte[] signature = null;
//Create the hash based on the document hash which is suitable for pdf siging with SHA256 and a X509Certificate
byte[] sh = pkcs7Signature.GetAuthenticatedAttributeBytes(hash, null, null, PdfSigner.CryptoStandard.CMS);
//Create the signature via own certificate
signature = CreateSignature(sh, Program.privateKeyFile, Program.privateKeyPassword);
pkcs7Signature.SetExternalDigest(signature, null, "RSA");
return pkcs7Signature.GetEncodedPKCS7(hash, null, null, null, PdfSigner.CryptoStandard.CMS);
}
public void ModifySigningDictionary(PdfDictionary signDic)
{
signDic.PutAll(sigDic);
}
private static X509Certificate[] CreateChain(String cert, String ca)
{
//Note: The root certificate could be omitted and it would still work
X509Certificate[] chainy = new X509Certificate[2];
X509CertificateParser parser = new X509CertificateParser();
chainy[0] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(cert))
.CertificateStructure);
chainy[1] = new X509Certificate(parser.ReadCertificate(Encoding.UTF8.GetBytes(ca))
.CertificateStructure);
return chainy;
}
#region "Create signature, will be done by an actual service"
private byte[] CreateSignature(byte[] hash, string privateKeyFile, string privateKeyPassword)
{
//Sign data directly with a X509Certificate
X509Certificate2 rootCertificateWithPrivateKey = new X509Certificate2();
byte[] rawData = System.IO.File.ReadAllBytes(privateKeyFile);
rootCertificateWithPrivateKey.Import(rawData, privateKeyPassword, X509KeyStorageFlags.Exportable);
using (var key = rootCertificateWithPrivateKey.GetRSAPrivateKey())
{
return key.SignData(hash, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
#endregion
}
}
Question: Is there a way to
Produce the hash of a pdf document "ahead of time" on one instance of the PdfReader
Create the signature
Apply the signature on a different instance of the PdfReader
This use case currently is not supported by iText, in each pass in particular
a different PDF ID is generated,
a different modification time is used, and
in case of AES encrypted PDFs the random numbers used for encryption are different.
It is possible to patch iText to use the same values in each pass, but before patching a library you should consider whether you can adapt your architecture to make a patch unnecessary.
In your case, for example, if you cannot keep the original PdfSigner instance, an alternative approach could be to have the original PdfSigner after hashing store its result file with dummy signature bytes (e.g. new byte[0]). Then, after retrieving the signature container, you can inject it into the stored file in a different service using PdfSigner.signDeferred as long as both service can access shared storage (or the first service can at least forward the file the the storage of the second service).
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 am new in cryptographic programming.
I want to create only digital signature in C# with CAdES format using Pkcs11Interop library without having data or document or message to sign, I want the signature string only to use it on any json or xml file generated from my application.
I used Pkcs11Interop library because I have a smart token "Cryptoki" with unmanaged PKCS#11 dll library which I have to use to make the signature.
Here is my sample code based on Pkcs11Interop open source samples.
using System;
using System.Collections.Generic;
using Net.Pkcs11Interop.Common;
using Net.Pkcs11Interop.HighLevelAPI;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Pkcs11InteropFactories factories = new Pkcs11InteropFactories();
using (IPkcs11Library pkcs11Library = factories.Pkcs11LibraryFactory.LoadPkcs11Library(factories, #"C:\eps2003csp11.dll", AppType.MultiThreaded))
{
ILibraryInfo libraryInfo = pkcs11Library.GetInfo();
foreach (ISlot slot in pkcs11Library.GetSlotList(SlotsType.WithOrWithoutTokenPresent))
{
ISlotInfo slotInfo = slot.GetSlotInfo();
if (slotInfo.SlotFlags.TokenPresent)
{
using (ISession session = slot.OpenSession(SessionType.ReadWrite))
{
session.Login(CKU.CKU_USER, #"000000");
IObjectHandle publicKey = null;
IObjectHandle privateKey = null;
GenerateKeyPair(session, out publicKey, out privateKey);
IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_CMS_SIG);
byte[] sourceData = ConvertUtils.Utf8StringToBytes(null);
byte[] signature = session.Sign(mechanism, privateKey, sourceData);
string vStringSignature = ConvertUtils.BytesToBase64String(signature);
Console.WriteLine("Signature: " + vStringSignature);
session.DestroyObject(privateKey);
session.DestroyObject(publicKey);
session.Logout();
}
}
}
}
}
static void GenerateKeyPair(ISession session, out IObjectHandle publicKeyHandle, out IObjectHandle privateKeyHandle)
{
// The CKA_ID attribute is intended as a means of distinguishing multiple key pairs held by the same subject
byte[] ckaId = session.GenerateRandom(20);
// Prepare attribute template of new public key
List<IObjectAttribute> publicKeyAttributes = new List<IObjectAttribute>();
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PRIVATE, false));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, "Digital Business ERP"));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ENCRYPT, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_VERIFY, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_VERIFY_RECOVER, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_WRAP, true));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_MODULUS_BITS, 1024));
publicKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PUBLIC_EXPONENT, new byte[] { 0x01, 0x00, 0x01 }));
// Prepare attribute template of new private key
List<IObjectAttribute> privateKeyAttributes = new List<IObjectAttribute>();
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_TOKEN, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_PRIVATE, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_LABEL, "Digital Business ERP"));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_ID, ckaId));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_SENSITIVE, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_DECRYPT, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_SIGN, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_SIGN_RECOVER, true));
privateKeyAttributes.Add(session.Factories.ObjectAttributeFactory.Create(CKA.CKA_UNWRAP, true));
// Specify key generation mechanism
IMechanism mechanism = session.Factories.MechanismFactory.Create(CKM.CKM_RSA_PKCS_KEY_PAIR_GEN);
// Generate key pair
session.GenerateKeyPair(mechanism, publicKeyAttributes, privateKeyAttributes, out publicKeyHandle, out privateKeyHandle);
}
}
}
If I put "null" or empty double quotes in the following sourceData variable
byte[] sourceData = ConvertUtils.Utf8StringToBytes(null);
or
byte[] sourceData = ConvertUtils.Utf8StringToBytes("");
I get error in creating signature like this
byte[] signature = session.Sign(mechanism, privateKey, sourceData);
or
byte[] signature = session.Sign(mechanism, privateKey, null);
Only when I put sample data like "Hello World" the signature string is generated successfully but it containes data string which is "Hello World" in this sample.
I want to generate signature string without any data contained, Also in my code I don't know how to set signature format to CAdES.
Tarek
I'm also struggling with the same issue, trying to produce a cades-bes signature on a json document and attach it to the original json document as a property.
This is as part of Egyptian tax authority rules for submitting signed invoices using their Api.
I tried the CMS classes of. Net core, and managed to produce the signature of the document, but the Api refuses my document, and I get a response of 400 "Bad request".
If you managed in your eduvor, please add the solution here.
I have 2 Projects in the same solution, in one project I encrypt a message using SignData and in the other project I am recreating the message that was encrypted in the first project and I compare the results to see if they are the same. But the encryption from the first project doesn't return the same value like the encryption from the second project. How can I make so that if i give the same byte array in both the projects I get the same value returned. I think that RSA generates 2 different keys but I use the same container
param.KeyContainerName = "SignatureContainer101";
Is there any way that I can do this without passing any Key? I need it to run locally on the same machine. Or any other suggestions in what I can do to get the same result?
This is the code I am using:
public static byte[] SignData(byte[] message)
{
//convert data to be signed in a byte array
byte[] byteSignature2;
CspParameters param = new CspParameters();
//Giving container a name, so we can use the same keys for verifying data.
param.KeyContainerName = "SignatureContainer101";
//Initializing RSACryptoServiceProvider
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(param))
{
//We want RSACryptoServiceProvider to save keys in keystore
//because we want to keep signature generation routine seperate
//from verifying data against signature.
rsa.PersistKeyInCsp = true;
byte[] byteSignature = rsa.SignData(message, "SHA256");
byteSignature2 = new byte[byteSignature.Length];
byteSignature2 = byteSignature;
}
return byteSignature2;
}
public static bool VerifyData(byte[] originalMessage, byte[] signedMessage)
{
bool isSuccess = false;
CspParameters param = new CspParameters();
//Utilizing the same key container which was created during
//signature generation process i.e. in GetDataSignature(string data) function.
//If we don't utilize the same keycontainer, VerifyData will always return false
//because RSACryptoSeriveProvider initialization will generate new keys.
param.KeyContainerName = "SignatureContainer101";
//Initializing RSACryptoServiceProvider
using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(param))
{
//verifying data against signature
isSuccess = rsa.VerifyData(originalMessage, "SHA256", signedMessage);
}
return isSuccess;
}
}
I am not sure what is the problem, exactly.
How can I make so that if i give the same byte array in both the projects I get the same value returned.
Do you expect SignData to return exactly the same bytes when called twice on an identical message with an identical key? It won’t and it shouldn’t. Every time an “identical” asymmetric (public-key) encryption/signing is done, it should have a different result. That is achieved using a random piece of padding used in the process. It is needed because in public-key cryptography, one of the keys is public, and if no randomness was used, it would make the cryptographic properties weaker. (For more information, see e.g. this answer on Security SE.)
So, you should not compare the result byte-by-byte, you should use the VerifyData method to check whether the signature is correct, not whether it is identical.
I found no problem in you code. It works! This is what I've done:
public partial class Program
{
protected override void Execute()
{
var message1 = Encoding.UTF8.GetBytes("Hello there,");
var message2 = Encoding.UTF8.GetBytes("Hello there");
var signed = SignData(message1);
var notOK = VerifyData(message2, signed);
var oK = VerifyData(message1, signed);
}
public static byte[] SignData(byte[] message)
{
byte[] byteSignature2;
var param = new CspParameters
{
KeyContainerName = "SignatureContainer101"
};
using (var rsa = new RSACryptoServiceProvider(param))
{
rsa.PersistKeyInCsp = true;
var byteSignature = rsa.SignData(message, "SHA256");
byteSignature2 = byteSignature;
}
return byteSignature2;
}
public static bool VerifyData(byte[] originalMessage, byte[] signedMessage)
{
bool isSuccess;
var param = new CspParameters
{
KeyContainerName = "SignatureContainer101"
};
using (var rsa = new RSACryptoServiceProvider(param))
isSuccess = rsa.VerifyData(originalMessage, "SHA256", signedMessage);
return isSuccess;
}
}
I need two methods one to encrypt and one to decrypt an xml file with a key= "hello world",the key hello world should be used to encrypt and decrypt the xml file.These methods should work on all machines!!! Any encryption methods will do. XML File contents below:
<root>
<lic>
<number>19834209</number>
<expiry>02/02/2002</expiry>
</lic>
</root>
Can some give me a sample?The issue is the msdn sample encyptions make a xml file encypted but when I decrypt on another machine it doesn't work.For example
I tried this sample:
How to: Encrypt XML Elements with Asymmetric Keys,
but here there is some kinda session and on another machine it says bad data phewf!
If you want the same key for encrypting and decrypting you should use a symmetric method (that's the definition, really). Here's the closest one to your sample (same source).
http://msdn.microsoft.com/en-us/library/sb7w85t6.aspx
The posted sample isn't working because they aren't using the same keys. Not only on different machines: running the program on the same machine twice should not work either (didn't work for me), because they use different random keys every time.
try adding this code after creating your key:
key = new RijndaelManaged();
string password = "Password1234"; //password here
byte[] saltBytes = Encoding.UTF8.GetBytes("Salt"); // salt here (another string)
var p = new Rfc2898DeriveBytes(password, saltBytes); //TODO: think about number of iterations (third parameter)
// sizes are devided by 8 because [ 1 byte = 8 bits ]
key.IV = p.GetBytes(key.BlockSize / 8);
key.Key = p.GetBytes(key.KeySize / 8);
Now the program is using the same key and initial vector, and Encrypt and Decrypt should work on all machines.
Also, consider renaming key to algorithm, otherwise this is very misleading. I'd say it's a bad, not-working-well example from MSDN.
NOTE: PasswordDeriveBytes.GetBytes() has been deprecated because of serious (security) issues within the PasswordDeriveBytes class. The code above has been rewritten to use the safer Rfc2898DeriveBytes class instead (PBKDF2 instead of PBKDF1). Code generated with the above using PasswordDeriveBytes may be compromised.
See also: Recommended # of iterations when using PKBDF2-SHA256?
First of all, if you want to use the same key for encrypting and decrypting, you should look at symmetric cryptography. Asymmetric cryptography is when the keys for encrypting and decrypting are different. Just so that you know - RSA is asymmetric, TripleDES and Rijndael are symmetric. There are others too, but .NET does not have default implementations for them.
I'd advise studying the System.Security.Cryptography namespace. And learning a bit about all that stuff. It has all you need to encrypt and decrypt files, as well as generate a password. In particular, you might be interested in these classes:
CryptoStream
PasswordDeriveBytes
RijndaelManaged
There are also examples for usage in MSDN for each of them. You can use these classes to encrypt any file, not just XML. If however you want to encrypt just a select few elements, you can take a look at System.Security.Cryptography.Xml namespace. I see you've already found one article about it. Keep following the links on that page and you will learn more about those classes.
Would be cooler if you used a private key to sign the <lic> element and added the result to the file (in a <hash> element perhaps). This would make it possibly for everyone to read the xml file in case your support needs to know the license number, or the date of expiry, but they can not change any values without the private key.
The public key needed to verify the signature would be common knowledge.
Clarification
Signing your code will only protect it against changes, it will not keep any information in it hidden. Your original question mentions encryption, but I am not sure that it is a requirement to hide the data, or just protect it from modification.
Example code: (Never publish PrivateKey.key. ServerMethods are only needed when signing the xml file, ClientMethods are only needed when verifying the xml file.)
using System;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
public static class Program {
public static void Main() {
if (!File.Exists("PublicKey.key")) {
// Assume first run, generate keys and sign document.
ServerMethods.GenerateKeyPair();
var input = new XmlDocument();
input.Load("input.xml");
Debug.Assert(input.DocumentElement != null);
var licNode = input.DocumentElement["lic"];
Debug.Assert(licNode != null);
var licNodeXml = licNode.OuterXml;
var signedNode = input.CreateElement("signature");
signedNode.InnerText = ServerMethods.CalculateSignature(licNodeXml);
input.DocumentElement.AppendChild(signedNode);
input.Save("output.xml");
}
if (ClientMethods.IsValidLicense("output.xml")) {
Console.WriteLine("VALID");
} else {
Console.WriteLine("INVALID");
}
}
public static class ServerMethods {
public static void GenerateKeyPair() {
var rsa = SharedInformation.CryptoProvider;
using (var keyWriter = File.CreateText("PublicKey.key"))
keyWriter.Write(rsa.ToXmlString(false));
using (var keyWriter = File.CreateText("PrivateKey.key"))
keyWriter.Write(rsa.ToXmlString(true));
}
public static string CalculateSignature(string data) {
var rsa = SharedInformation.CryptoProvider;
rsa.FromXmlString(File.ReadAllText("PrivateKey.key"));
var dataBytes = Encoding.UTF8.GetBytes(data);
var signatureBytes = rsa.SignData(dataBytes, SharedInformation.HashAlgorithm);
return Convert.ToBase64String(signatureBytes);
}
}
public static class ClientMethods {
public static bool IsValid(string data, string signature) {
var rsa = SharedInformation.CryptoProvider;
rsa.FromXmlString(File.ReadAllText("PublicKey.key"));
var dataBytes = Encoding.UTF8.GetBytes(data);
var signatureBytes = Convert.FromBase64String(signature);
return rsa.VerifyData(dataBytes, SharedInformation.HashAlgorithm, signatureBytes);
}
public static bool IsValidLicense(string filename) {
var doc = new XmlDocument();
doc.Load(filename);
var licNode = doc.SelectSingleNode("/root/lic") as XmlElement;
var signatureNode = doc.SelectSingleNode("/root/signature") as XmlElement;
if (licNode == null || signatureNode == null) return false;
return IsValid(licNode.OuterXml, signatureNode.InnerText);
}
}
public static class SharedInformation {
public static int KeySize {
get { return 1024; }
}
public static string HashAlgorithm {
get { return "SHA512"; }
}
public static RSACryptoServiceProvider CryptoProvider {
get { return new RSACryptoServiceProvider(KeySize, new CspParameters()); }
}
}
}
this is how you digitally sign and verify XML documents Sign XML Documents
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);
}