Securing a license key with RSA key - c#

it's late, I'm tired, and probably being quite dense....
I have written an application that I need to secure so it will only run on machines that I generate a key for.
What I am doing for now is getting the BIOS serial number and generating a hash from that, I then am encrypting it using a XML RSA private key. I then sign the XML to ensure that it is not tampered with.
I am trying to package the public key to decrypt and verify the signature with, but every time I try to execute the code as a different user than the one that generated the signature I get a failure on the signature.
Most of my code is modified from sample code I have found since I am not as familiar with RSA encryption as I would like to be. Below is the code I was using and the code I thought I needed to use to get this working right...
Any feedback would be greatly appreciated as I am quite lost at this point
the original code I was working with was this, this code works fine as long as the user launching the program is the same one that signed the document originally...
CspParameters cspParams = new CspParameters();
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
// Create a new RSA signing key and save it in the container.
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider(cspParams)
{
PersistKeyInCsp = true,
};
This code is what I believe I should be doing but it's failing to verify the signature no matter what I do, regardless if it's the same user or a different one...
RSACryptoServiceProvider rsaKey = new RSACryptoServiceProvider();
//Load the private key from xml file
XmlDocument xmlPrivateKey = new XmlDocument();
xmlPrivateKey.Load("KeyPriv.xml");
rsaKey.FromXmlString(xmlPrivateKey.InnerXml);
I believe this to have something to do with the key container name (Being a real dumbass here please excuse me) I am quite certain that this is the line that is both causing it to work in the first case and preventing it from working in the second case....
cspParams.KeyContainerName = "XML_DSIG_RSA_KEY";
Is there a way for me to sign/encrypt the XML with a private key when the application license is generated and then drop the public key in the app directory and use that to verify/decrypt the code? I can drop the encryption part if I can get the signature part working right. I was using it as a backup to obfuscate the origin of the license code I am keying from.
Does any of this make sense?
Am I a total dunce?
Thanks for any help anyone can give me in this..

I used this method to sign xml documents using a private key stored in an xml file that I then embedded into the application .dll as a resource. I think you may be struggling with permissions accessing the keystore, and this would also create hassles transferring the code to other servers etc.
Here is the code to get the private key as an embedded resource and sign the document:
(Sign is the name of the class this method is located in, Licensing.Private.Private.xml is a combination of the default namespace + folder + filename of the resource)
public static void SignDocument(XmlDocument xmldoc)
{
//Get the XML content from the embedded XML privatekey.
Stream s = null;
string xmlkey = string.Empty;
try
{
s = typeof(Sign).Assembly.GetManifestResourceStream("Licensing.Private.Private.xml");
// Read-in the XML content.
StreamReader reader = new StreamReader(s);
xmlkey = reader.ReadToEnd();
reader.Close();
}
catch (Exception e)
{
throw new Exception("Error: could not import key:",e);
}
// Create an RSA crypto service provider from the embedded
// XML document resource (the private key).
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.FromXmlString(xmlkey);
//Creating the XML signing object.
SignedXml sxml = new SignedXml(xmldoc);
sxml.SigningKey = csp;
//Set the canonicalization method for the document.
sxml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigCanonicalizationUrl; // No comments.
//Create an empty reference (not enveloped) for the XPath transformation.
Reference r = new Reference("");
//Create the XPath transform and add it to the reference list.
r.AddTransform(new XmlDsigEnvelopedSignatureTransform(false));
//Add the reference to the SignedXml object.
sxml.AddReference(r);
//Compute the signature.
sxml.ComputeSignature();
// Get the signature XML and add it to the document element.
XmlElement sig = sxml.GetXml();
xmldoc.DocumentElement.AppendChild(sig);
}
Use the following code the generate the private.xml and public.xml keys. Keep the private.xml file secure, obviously.
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
File.WriteAllText(#"C:\privateKey.xml", rsa.ToXmlString(true)); // Private Key
File.WriteAllText(#"C:\publicKey.xml", rsa.ToXmlString(false)); // Public Key

Guess, the problem is that different users don't have access to the key that is stored for the 1st user (Please note: I am not a cryptography expert).

Related

Get/Read Public Certificate for PDF Encryption using .pfx file with password through itext7 library/method

I have iText7 functions which I am using,
right now, I am trying to encrypt my PDF file using a certificate in .pfx format with password.
The thing is, the function cannot read .pfx because it does not provide the password as shown below
using System.IO;
using iText.Kernel.Pdf;
using Org.BouncyCastle.X509;
namespace iText.Samples.Sandbox.Security
{
public class EncryptWithCertificate
{
public static readonly String DEST = "results/sandbox/security/encrypt_with_certificate.pdf";
public static readonly String SRC = "../../../resources/pdfs/hello.pdf";
public static readonly String PUBLIC = "../../../resources/encryption/test.cer";
public static void Main(String[] args)
{
FileInfo file = new FileInfo(DEST);
file.Directory.Create();
new EncryptWithCertificate().ManipulatePdf(DEST);
}
public X509Certificate GetPublicCertificate(String path)
{
using (FileStream stream = File.Open(path, FileMode.Open))
{
X509CertificateParser parser = new X509CertificateParser();
X509Certificate readCertificate = parser.ReadCertificate(stream);
return readCertificate;
}
}
protected void ManipulatePdf(String dest)
{
// The file created by this example can not be opened, unless
// you import the private key stored in test.p12 in your certificate store.
// The password for the p12 file is kspass.
X509Certificate cert = GetPublicCertificate(PUBLIC);
PdfDocument document = new PdfDocument(new PdfReader(SRC), new PdfWriter(dest,
new WriterProperties().SetPublicKeyEncryption(
new[] {cert},
new[] {EncryptionConstants.ALLOW_PRINTING},
EncryptionConstants.ENCRYPTION_AES_256)));
document.Close();
}
}
}
If i try to load a normal .cer file, it goes through normally for GetPublicCertificate. No issue there. But I am trying to encrypt it with .pfx file as adobe acrobat can only register Digital ID using .p12/.pkf format and the function does throws error.
The error
Org.BouncyCastle.Security.Certificates.CertificateException
HResult=0x80131500
Message=Failed to read certificate
Inner Exception 1:
ArgumentException: Unknown object in GetInstance: Org.BouncyCastle.Asn1.DerInteger
Parameter name: obj
I am hoping to encrypt the pdf using cert as the cert can be set to expire anytime according to what I set it to be and user can only view the PDF file based on the cert expiry.
Thanks in advance.
As per your original post, your intention is:
I am hoping to encrypt the pdf using cert as the cert can be set to expire anytime according to what I set it to be and user can only view the PDF file based on the cert expiry.
Basically, you want to grant a time-limited access to PDF to authorized user. The solution you try to build in code sample doesn't solve the problem. Certificate validity for data encryption is irrelevant, because certificate validity is not checked during decryption. In fact, even certificate is not necessary, it is sufficient to have just a private key to decrypt the data. In other words, certificate-based encryption is equal to password-based encryption. What certificate adds -- an easier way to locate decryption key (secret), nothing else.
In addition, once data is decrypted, a client can save data in an unencrypted form thus your restrictions are useless. Even if you try to put time constraints within JavaScript or whatever else locally (and JavaScript is executed only locally), it isn't a solution. As long as client can manipulate date/time on a device, client always can set desired date/time to violate your restrictions.
Your problem cannot be solved without inventing a 3rd party entity that will make decisions whether the requested operation is allowed, apply necessary restrictions and minimize chances that the data will leak in an unencrypted form (only minimize, not prevent). Such functionality is implemented in Digital Rights Management (DRM) or Rights Management Service (RMS) and you need to build your solution around these tools, not attempt to integrate them in your solution. There are plenty of vendors that offer DRM/RMS solutions you can look into and utilize their functionality to build the solution for your requirements.

Load ASN.1/DER encoded RSA keypair in C#

I have DER encoded RSA keypair created in Crypto++, as well as cipher. They are Base64Encoded string. I first decode the data from Base64 to byte array, but I am not sure how to load them into RSACryptoServiceProvider.
static void Main()
{
string pbkeystr = "mypublickey";
string pvkeystr = "myprivatekey";
string cipherstr = "mycipher";
byte[] pbkey = Convert.FromBase64String(pbkeystr);
byte[] pvkey = Convert.FromBase64String(pvkeystr);
byte[] cipher = Convert.FromBase64String(cipherstr);
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
//Set keys here..
//Decrypt the cipher using private key
rsa.Decrypt(pvkey, false);
}
There are no functions to set keys. The only thing I found was ImportParameters method, which takes RSAParameters class which consists of p, q, n, modulus, exponent etc. I don't have access to these.
Is there any way I can load the keys as string? How can I load the key into RSACryptoServiceProvider?
Is there any way I can load the keys as string? How can I load the key into RSACryptoServiceProvider?
From your other Crypto++ question, How to load Base64 RSA keys in Crypto++, it looks like you have only the public and private keys because you used DEREncode and BERDecode. That is, you have the RSA parameters, and not the subject public key info and the private key info. Your keys lack the OID identifiers and version numbers. Things are fine that way.
From Cryptographic Interoperability: Keys on the Code Project, you will need a C# class that parses the ASN.1/DER after you Base64 decode it. The CodeProject article provides a C# class called AsnKeyParser to read the ASN.1/DER and returns a RSAParameters to load into a CSP.
The code for the AsnKeyParser class is about 800 lines, and there are five other supporting files to make it all happen, so its not really appropriate to place it here. You should download it yourself. The file of interest is called CSInteropKeys.zip.
Once you wire-in the AsnKeyParser class, it will be as simple as the following for a RSA Public key. The private key will be similar, and the code is given on the CodeProject site.
// Your ASN.1/DER parser class
AsnKeyParser keyParser = new AsnKeyParser("rsa-public.der");
RSAParameters publicKey = keyParser.ParseRSAPublicKey();
// .Net class
CspParameters csp = new CspParameters;
csp.KeyContainerName = "RSA Test (OK to Delete)";
csp.ProviderType = PROV_RSA_FULL; // 1
csp.KeyNumber = AT_KEYEXCHANGE; // 1
// .Net class
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(csp);
rsa.PersistKeyInCsp = false;
rsa.ImportParameters(publicKey);
Linking to files on another site is frowned upon, but I don't know how to provide the information otherwise. There's too much source code involved to place in an answer.
For completeness, .Net does not make interop easy. They do not accept ASN.1/DER or PEM. Rather, .Net accepts some XML representation of the keys. I believe you can find it in RFC 3275, XML-Signature Syntax and Processing. Microsoft does not state that for you. I kind of pieced it together when I wrote the Code Project article.
Maybe we should add a class to Crypto++ to regurgitate XML in addition to ASN.1/DER and PEM.

Is it possible to sign an xml document without having to use KeyContainerName?

I want to create 2 really simple dlls:
1) that will sign an xml document
2) that will check that the xml document hasnt been modified.
I tried using the RSACryptoServiceProvider and a key container. But when I move to a different machine this does not work as the key is being stored in the machine.
I want to store the key in the dlls im creating (I know this is not reccomended) but I just cannot work out how to write some code to simply sign an xml document and then verify that it hasn't been changed.
So do I need to use symmetric key to do what I want is this possible?
Pete
You already mention the problems with storing the private key in the dll, so I won't repeat that.
Do this:
On your own machine run this code:
var key = new RSACryptoServiceProvider(2048);
string publicKey = key.ToXmlString(false);
string privateKey = key.ToXmlString(true);
Console.WriteLine(publicKey);
Console.WriteLine(privateKey);
this outputs two (long) lines. Copy those into your code:
Sign:
var privateKey = new RSACryptoServiceProvider();
privateKey.FromXmlString(/* insert the private-key XML string here */ );
privateKey.SignData(/*...*/);
Verify:
var publicKey = new RSACryptoServiceProvider();
publicKey.FromXmlString(/* insert the public-key XML string here */ );
publicKey.VerifyData(/*...*/);
If it is just about to verify that your xml document hasn't been modified a simple MD5 checksum (or any other good hashing algorithm) would be easier to implement and is what you need. It would be also verifyable on different machines.

RSACryptoServiceProvider KeyContainer appears to time out?

I am using the RSACryptoServiceProvider like this...
private byte[] RSAEncrypt(byte[] DataToEncrypt, string ContainerName, bool DoOAEPPadding)
{
try
{
byte[] encryptedData;
// Create a new instance of CspParameters. Pass
// 13 to specify a DSA container or 1 to specify
// an RSA container. The default is 1.
CspParameters cspParams = new CspParameters();
// Specify the container name using the passed variable.
cspParams.KeyContainerName = ContainerName;
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
//Create a new instance of RSACryptoServiceProvider.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cspParams))
{
//Encrypt the passed byte array and specify OAEP padding.
//OAEP padding is only available on Microsoft Windows XP or
//later.
encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
}
return encryptedData;
}
//Catch and display a CryptographicException
//to the console.
catch (CryptographicException ex)
{
sl.Write(ex, MessageType.Error);
throw;
}
}
I then try to decrypt the data after turning off my Outlook Plugin Windows Form application and turning it back on which is what is using this peice of code. The decrypt code looks like this...
private byte[] RSAEncrypt(byte[] DataToEncrypt, string ContainerName, bool DoOAEPPadding)
{
try
{
byte[] encryptedData;
// Create a new instance of CspParameters. Pass
// 13 to specify a DSA container or 1 to specify
// an RSA container. The default is 1.
CspParameters cspParams = new CspParameters();
// Specify the container name using the passed variable.
cspParams.KeyContainerName = ContainerName;
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
//Create a new instance of RSACryptoServiceProvider.
using (RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(cspParams))
{
//Encrypt the passed byte array and specify OAEP padding.
//OAEP padding is only available on Microsoft Windows XP or
//later.
encryptedData = RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
}
return encryptedData;
}
//Catch and display a CryptographicException
//to the console.
catch (CryptographicException ex)
{
sl.Write(ex, MessageType.Error);
throw;
}
}
Works great until something comes up that I can not put my finger on. I don't know if it is like the date changes or what. What happens is that I try to decrypt the data and I get a "bad data" error. Now again it works great until some elapsed period of time, or turning off the app, or the user logging off. I just don't know and can't determine what causes it. The moment I blow away the encrypted data which comes from a text file and recreate it and decrypt it I have no problem. Even if I restart the application in between encrypting/saving to file and the reading from file/decrypting it will works great! Something happens and I just don't know KeyContainers well enough to understand what could possibly make the CspParameters expire is my best guess?
You could try using the bouncy castle crypto libraries if you're really stuck:
http://www.bouncycastle.org/csharp/
I ended up using the CspParameters flag and instead of using the Users KeyContainer store I used the Machine KeyContainer Store.
Yes, if you set:
cspParams.Flags = CspProviderFlags.UseDefaultKeyContainer;
then the key container is stored in the user's key container store, then logging on as another user will and using RSA with present you with an entirely different KeyContainer store.
Using this instead:
cspParams.Flags = CspProviderFlags.UseMachineKeyStore = true;
Will use the local machine's KeyContainer store, which is global for the machine, and will provide you with the same KeyContainer store, irregardless of which user is logged in. However, this only applies for that windows installation. Running your program under a different windows installation or machine will provide you with a different KeyContainer store. If you wish to decrypt the same data across multiple machines, you will need to persist your key to a file on the hard drive. Persisting a key to a plain text file is a huge security risk, so please encrypt your key before persisting it to a file, or put it in a password protected .rar files or something.
If your still having issues, try setting:
RSA.PersistKeyInCsp = true;
This will ensure that your key is persisted in the KeyContainer store. Persisting the file in the KeyContainer should be the default behavior if you use the CspParameters constructor such as:
CspParameters cspParams = new CspParameters();
In Microsoft's own words:
"This form of CspParameters initializes the ProviderType field to a value of 24, which specifies the PROV_RSA_AES provider."
Source: http://msdn.microsoft.com/en-us/library/xw9ywed4.aspx
So your comments in your code is incorrect and my be misleading you. I would advise you to correct them.
I am unsure about other ProviderTypes and their default settings regarding persisting the key in the KeyContainer store, so setting PersistKeyInCsp to TRUE might be necessary if your still having issues.
Hope this helps.
~Adam WhiteHat();

using RSACryptoServiceProvider or RSA in hosted environment

I would like to use Public/Private encryption where my web app would sign an xml document or a piece of data using a private key and it will be verified in the application using the public key,
Is it possible to use RSACryptoServiceProvider in .net 2.0 hosted environment if not what are other possible workarounds?
Yes, quite possible.
If you export the cert you will use for data-signing into a .pfx file, you can get a digsig this way.
using System;
using System.Xml;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography.X509Certificates;
// ...
static void SignWithPfxPrivateKey()
{
X509Certificate2 certificate = new X509Certificate2(certFile, pfxPassword);
RSACryptoServiceProvider rsaCsp = (RSACryptoServiceProvider) certificate.PrivateKey;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.PreserveWhitespace = true;
if (loadFromString)
xmlDoc.LoadXml(rawXml); // load from a string
else
xmlDoc.Load("test.xml"); // load from a document
// Sign the XML document.
SignXml(xmlDoc, rsaCsp);
// Save the document.
xmlDoc.Save("RsaSigningWithCert.xml");
xmlDoc.Save(new XTWFND(Console.Out));
}
public static void SignXml(XmlDocument Doc, RSA Key)
{
// Check arguments.
if (Doc == null)
throw new ArgumentException("Doc");
if (Key == null)
throw new ArgumentException("Key");
// Create a SignedXml object.
SignedXml signedXml = new SignedXml(Doc);
// Add the key to the SignedXml document.
signedXml.SigningKey = Key;
// Create a reference to be signed.
Reference reference = new Reference();
reference.Uri = "";
// Add an enveloped transformation to the reference.
XmlDsigEnvelopedSignatureTransform env = new XmlDsigEnvelopedSignatureTransform();
reference.AddTransform(env);
// Add the reference to the SignedXml object.
signedXml.AddReference(reference);
// Compute the signature.
signedXml.ComputeSignature();
// Get the XML representation of the signature and save
// it to an XmlElement object.
XmlElement xmlDigitalSignature = signedXml.GetXml();
// Append the element to the XML document.
Doc.DocumentElement.AppendChild(Doc.ImportNode(xmlDigitalSignature, true));
}
To use this, you will have to upload a .pfx file to the hosted server. You also need a password for that pfx file, which you should store securely in application config settings. You can do something similar with a .cer file.
You can also load the RSA key from regular XML. If you have an RSACryptoServiceProvider that you instantiated by loading a key from your local machine store, like this:
// Get the key pair from the key store.
CspParameters parms = new CspParameters(1);
parms.Flags = CspProviderFlags.UseMachineKeyStore;
parms.KeyContainerName = "SnapConfig";
parms.KeyNumber = 2;
RsaCsp = new RSACryptoServiceProvider(parms);
...then you can export that key with
RsaCsp.ToXmlString(true);
The result is a string, containing an XML document that you can save to a file, and then upload to the hosted server. An alternative to the .pfx file as a store for your key pair. Be careful. This xml document is not protected with a password, and is insecure. (read the doc on ToXmlString()) You can encrypt this XML doc, just as you would encrypt any other settings. (search)
In that case you can do this to get your csp:
RSACryptoServiceProvider csp = new RSACryptoServiceProvider();
csp.FromXmlString(keyPairXml);
See here: http://www.codeproject.com/KB/security/EZRSA.aspx
Yes, it is possible to use this in a hosted environment, but beware of the multi threaded issues relating to its use
These are some suggestions to minimize threading issues in ASP.NET:
Set CspParameters.KeyContainer name when creating an RSACryptoServiceProvider object. Never use the default one, NEVER!
This is prone to store corruption. We talked about this already.
Explicitely call Dispose on the RSACrytpoServiceProvider object when possible. This way, we don't rely on the Garbage Collector to
"eventually" release the CSP.
Set UseMachineKeyStore in CspParameters.Flags when creating an RSACryptoServiceProvider. This flag tells RSACryptoServiceProvider to
use "machine location" for storing RSA public/private key pair instead
of the "current user" location. This is equivalent to calling
CryptAcquireContext API with CRYPT_MACHINE_KEYSET. Make sure all the
threads using the RSA instance are running under the same account.
Don't set RSACryptoServiceProvider.PersistKeyInCSP to False. This will cause the key container to be deleted when RSA instance is
released or garbage collected. If multiple threads are trying to
access the same named key container, the state of that named key
container could be in a temporary cleaned up state and it cannot be
acquired.
Another option would be for us to instantiate RSACryptoServiceProvider once as part of Web Application
initialization and then use that same instance for encryption or
decryption under a critical section like lock (i.e. Mutex). This would
also avoid reacquiring key container multiple times from the same
process.

Categories