How to extract PKCS#1 signature from PKCS#7 - c#

I know that PKCS#7 = Certificate + Optional raw data + Signature in PKCS#1 format I need to extract PKCS#1 from a PKCS#7 signature how can I do this in C#. Can I use the bouncy castle to do this, Here is my implementation
ie. to convert PKCS#7 to ASN.1 and to take the last sequence as it is PKCS#1
Asn1InputStream asn1 = new Asn1InputStream(pkcs7Stream);
Asn1Sequence sequence = (Asn1Sequence)asn1.ReadObject().ToAsn1Object();
var sequenceString = sequence.ToString();
var lastCommaIndex = sequenceString.LastIndexOf(",");
var pkcs1HexStr = sequenceString.Substring(lastCommaIndex + 3).Replace("]", string.Empty);
Is there any other eligant way to obtain PKCS#1

The SignedCms class can do this for you, .NET Core 2.1+ or .NET Framework 4.7.2+:
SignedCms cms = new SignedCms();
cms.Decode(message);
return cms.SignerInfos[0].GetSignature();
Assuming you want the signature from the first signer, of course. (The GetSignature method is what requires net472+)
Other signers or countersigners would also be available, just through different aspects of the object model.

Thanks, #bartonis for the help and guidance
Here is implementation using bouncy castle
public static byte[] GetRaw(byte[] input)
{
SignerInfo signerInfo = GetSignerInfo(input);
return signerInfo?.EncryptedDigest?.GetOctets();
}
private static SignerInfo GetSignerInfo(byte[] input)
{
Asn1InputStream cmsInputStream = new Asn1InputStream(input);
Asn1Object asn1Object = cmsInputStream.ReadObject();
Asn1Sequence asn1Sequence = Asn1Sequence.GetInstance(asn1Object);
SignedData signedData = GetSignedData(asn1Sequence);
SignerInfo signerInfo = GetSignerInfo(signedData);
if (signerInfo?.UnauthenticatedAttributes != null)
{
signedData = GetSignerInfo(signerInfo);
signerInfo = GetSignerInfo(signedData);
}
return signerInfo;
}
private static SignerInfo GetSignerInfo(SignedData signedData)
{
Asn1Encodable[] Asn1Encodables = signedData?.SignerInfos?.ToArray();
if (Asn1Encodables != null)
{
if (Asn1Encodables.Length > 0)
{
SignerInfo signerInfo = SignerInfo.GetInstance(Asn1Encodables[0]);
return signerInfo;
}
}
return null;
}
private static SignedData GetSignedData(Asn1Sequence sequence)
{
var rootContent = ContentInfo.GetInstance(sequence);
var signedData = SignedData.GetInstance(rootContent.Content);
return signedData;
}
private static SignedData GetSignerInfo(SignerInfo signerInfo)
{
Asn1Encodable[] asn1Encodables = signerInfo.UnauthenticatedAttributes.ToArray();
foreach (var asn1Encodable in asn1Encodables)
{
Asn1Sequence sequence = Asn1Sequence.GetInstance(asn1Encodable);
DerObjectIdentifier OID = (DerObjectIdentifier)sequence[0];
if (OID.Id == "1.2.840.113549.1.9.16.2.14")
{
Asn1Sequence newSequence =Asn1Sequence.GetInstance(Asn1Set.GetInstance(sequence[1])[0]);
SignedData signedData = GetSignedData(newSequence);
return signedData;
}
}
return null;
}

Related

Decrypt and verify hash for SHA256 - c#

I am trying to decrypt and validate the hash but at times of decrypting, it throws me the error 'Key does not exist' and at the time of validating the hash return False
https://payvyne.readme.io/docs/webhooks
Signature:
HEjoCsghC9X0slrE2DprptDLYdoA7jaw4Jl7vpJVxzx9GNJEiO3pYGLDPhLmVqk98QJJ/FuiS5J+fvp+msr3Y8aFzKqjRQXj5TBELT38N+A7I8y3Vc0mgeR0aDMx7I83yhfkcoyhdiGJibzqQ5SYFZ0nnEVHYXheLUlga45yg/McDICtMm6lhnrPWEuHzoZTQkhsrLN/1W1PtLjJ2DickWB78PmhpeflL2Cpe6qS3qCclqFGZ7HIl9OoxU4WXpTYgxw7eixAKB7apFdFqea4BnGravfENNl97pOBuU6fRof4KtMczVagQw3QnxFD3BBtpTepRaT+jHY8wStXUG1bxllH32WiA9CVcpY4mxKhpxzQ8YD0b+3OgkpzZYS+BVVAdVazMJeEAw7v/zaxpjbR+Zo5l9vOLdyatwM75qpwMoKnMeKJHeRytEOK54al49OHiaE+v1OkOhJA0zh5nLzEIZanIdf+hXHDz3Euecs/p0cABiFNmhzYY5fl8qEytK6j2CjXQOYgljG5dqPm7M9CW36ntZTDaIEVWql3jdi9frxc4/82w1jhROFL0pBG1zz8nimAEesB1AaxmNqW7BIxULweX7eaReeo/dIqDSbmFuT+TikPQo4XRtmpDqO37Y9P6q7ZXtHOFopSaykHUHs+NgrKlBJMM5ADg5bHWm2Qows=
Public key:
pA6ULfXWrIMq-qvxn_0CykoStq0ZMYm63lHsuXTsE4q4tgekLJDW2Lnf35ilbFU_vybBdyeJAphpsYc4P0eJBt_z2T62HAV3gnwp_GU6hWIo8faK31TSXIrLmGjZlAVynAxjFYZoNxMeZuwEXpxG4bRGs58P7XSx1fAzedX6oGIlcSLljKH4I1BHt6gJhPIHYNXQzq_a0hX54C1m1VDVP_kot8ui1YKZil_riROK_Xk4ktnOTAqXo9z4uNBqzzH2k0J2YNiCb8VOdbp7kjmH9sPLI-jb-ociy0wSkGZc1e8saGIkkSm4eUASvX_M_TTDD99OrgoIS2Vx07Tw4lK5yd28EMVBUzy2OypuPVf9PyoDGv_4241x5PpJsA9IKocD7AgwxJ3E7FBFhvuSP8c5wspkbQxBwv5nnk2zAxuZsiJeK0o3JSxjkZJEkeVY4mA3VV9SvSXEKAFg2h9J3CR9PTwrZoVBruycVtWJ4it5jroXff-aGlLoRAO0g3gtfjkJb3tw6SJTFOA49iJci76Mj8Adz3eeEEGxTxfDzh_lq0jXxTk7cQSaR2_ChYLHaoorrrFmAvWgDH_lSvlISIgey-SzUoJM9RAy4gVFdmg-XCQQlpMh_d1-IACO3EfBvYKWE-6uGIqx1nZhn9WIDdSqMp6940xRxl0vQy8vYCQ5q8U
Data for Sign in string:
{"type":"PAYMENT_STATUS_CHANGE","paymentId":"1c6e834f074ec941","status":"FAILED","timestamp":1652688286662,"amount":"164.69","currency":"GBP","description":"This is test payment","paymentType":"ONE_OFF","bankName":"Diamond bank","destinationAccount":"GBP2","createdAt":"2022-05-16T08:04:32.994","updatedAt":"2022-05-16T08:04:46.662","customerReference":"1199","refundedAmount":"0.00"}
Expo (exponent):
AQAB
Below is the code to Decrypt the signature using public key.
public static void DecryptUsingPublicKey(string publicKey, string expo, string signature)
{
var modulus = ConvertToBase64(publicKey);
var exponent = Convert.FromBase64String(expo);
RSACryptoServiceProvider csp = new RSACryptoServiceProvider(2048);
var _publicKey = csp.ExportParameters(false);
_publicKey.Modulus = modulus;
_publicKey.Exponent = exponent;
csp.ImportParameters(_publicKey);
var dataBytes = ConvertToBase64(signature);
var plainText = csp.Decrypt(dataBytes, false);
var returnData = Encoding.Unicode.GetString(plainText);
Console.WriteLine($"value: {returnData}");
}
Below is the code for Verify signature using public key
public static void VerifySignature(string signature, string pKey, string dataForSign)
{
string pKeyNew = pKey;
pKeyNew = pKeyNew.Replace("_", "/").Replace("-", "+");
string publicKey = $"<RSAKeyValue><Modulus>{pKeyNew}==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
var encoder = new UTF8Encoding();
byte[] dataForSignAsBytes = encoder.GetBytes(dataForSign);
byte[] signatureAsBytes = ConvertToBase64(signature);
RSACryptoServiceProvider rsaCryptoServiceProvider = new RSACryptoServiceProvider();
rsaCryptoServiceProvider.FromXmlString(publicKey);
var hashData = SHA256.Create().ComputeHash(dataForSignAsBytes);
var result1 = rsaCryptoServiceProvider.VerifyData(dataForSignAsBytes, CryptoConfig.MapNameToOID("SHA256"), signatureAsBytes);
var result2 = rsaCryptoServiceProvider.VerifyHash(hashData, CryptoConfig.MapNameToOID("SHA256"), signatureAsBytes);
var result3 = rsaCryptoServiceProvider.VerifyHash(hashData, signatureAsBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
var result4 = rsaCryptoServiceProvider.VerifyData(dataForSignAsBytes, signatureAsBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
Console.WriteLine(result1);
Console.WriteLine(result2);
Console.WriteLine(result3);
Console.WriteLine(result4);
}
ConvertToBase64 function
public static byte[] ConvertToBase64(string data)
{
byte[] cyperBuffer;
string dataNew = data;
dataNew = dataNew.Replace("_", "/").Replace("-", "+");
try
{
if (dataNew.Substring(dataNew.Length - 1) != "=")
{
dataNew += "=";
}
cyperBuffer = Convert.FromBase64String(dataNew);
}
catch
{
dataNew += "=";
try
{
cyperBuffer = Convert.FromBase64String(dataNew);
}
catch
{
//If any error occured while convert to base64 then append '=' at the end.
dataNew += "=";
cyperBuffer = Convert.FromBase64String(dataNew);
}
}
return cyperBuffer;
}
This is a conversion mistake; you need to decode the base 64 signature, not encode the signature, so the following line is wrong:
byte[] signatureAsBytes = ConvertToBase64(signature);
it should be something like:
byte[] signatureAsBytes = ConvertFromBase64(signature);
Decryption is modular exponentiation with a private key. Furthermore, encryption normally uses a different padding scheme than signature generation, so you'd expect that the unpadding would fail if you try and decrypt. Only verification is possible.

Encrypt string using public key as text in C#

I have recieved a public keY in txt format. (BEGIN CERTIFICATE---END CERTIFICATE)
I want to encrypt my message using this key in C# and send it across.
Similarly I have my private key in text format. I have shared my public key with the third party, which they are using to encrypt the message. I want to decrypt the message using my private key in TEXT format.
How do i do that in C#?
Kindly help.
public class MyCrypto
{
public X509Certificate2 GetDecryptionCertificate(string certificateName)
{
var my = new X509Store(StoreName.My, StoreLocation.LocalMachine);
my.Open(OpenFlags.ReadOnly);
var collection = my.Certificates.Find(X509FindType.FindBySubjectName, certificateName, false);
if (collection.Count == 1)
{
return collection[0];
}
else if (collection.Count > 1)
{
throw new Exception(string.Format("More than one certificate with name '{0}' found in store LocalMachine/My.", certificateName));
}
else
{
throw new Exception(string.Format("Certificate '{0}' not found in store LocalMachine/My.", certificateName));
}
}
public X509Certificate2 GetEncryptionCertificate(string filePath)
{
var collection = new X509Certificate2Collection();
collection.Import(filePath);
return collection[0];
}
public string EncryptRsa(string input, X509Certificate2 x509Certificate2)
{
var output = string.Empty;
using (RSA csp = (RSA)x509Certificate2.PublicKey.Key)
{
byte[] bytesData = Encoding.UTF8.GetBytes(input);
byte[] bytesEncrypted = csp.Encrypt(bytesData, RSAEncryptionPadding.OaepSHA1);
output = Convert.ToBase64String(bytesEncrypted);
}
return output;
}
public string DecryptRsa(string encrypted, X509Certificate2 x509Certificate2)
{
var text = string.Empty;
using (RSA csp = (RSA)x509Certificate2.PrivateKey)
{
byte[] bytesEncrypted = Convert.FromBase64String(encrypted);
byte[] bytesDecrypted = csp.Decrypt(bytesEncrypted, RSAEncryptionPadding.OaepSHA1);
text = Encoding.UTF8.GetString(bytesDecrypted);
}
return text;
}
}

Certificate extension value contains 2 extra bytes (\u0004\u0002) octet encoding

for testing purposes my unittest generates a test certificate with custom extensions using BouncyCastle for .NET core.
Generate function
static internal class CertificateGenerator
{
public static X509Certificate2 GenerateCertificate(string region)
{
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var certificateGenerator = new X509V3CertificateGenerator();
var serialNumber =
BigIntegers.CreateRandomInRange(
BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
const string signatureAlgorithm = "SHA1WithRSA";
certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm);
var subjectDN = new X509Name("CN=FOOBAR");
var issuerDN = subjectDN;
certificateGenerator.SetIssuerDN(issuerDN);
certificateGenerator.SetSubjectDN(subjectDN);
var notBefore = DateTime.UtcNow.Date.AddHours(-24);
var notAfter = notBefore.AddYears(1000);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
var fakeOid = "1.3.6.1.1.5.6.100.345434.345";
if (region != null)
{
certificateGenerator.AddExtension(new DerObjectIdentifier(fakeOid), false, Encoding.ASCII.GetBytes(region));
}
const int strength = 4096;
var keyGenerationParameters = new KeyGenerationParameters(random, strength);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
var issuerKeyPair = subjectKeyPair;
var certificate = certificateGenerator.Generate(issuerKeyPair.Private, random);
var store = new Pkcs12Store();
string friendlyName = certificate.SubjectDN.ToString();
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(friendlyName, certificateEntry);
store.SetKeyEntry(friendlyName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
string password = "password";
var stream = new MemoryStream();
store.Save(stream, password.ToCharArray(), random);
byte[] pfx = Pkcs12Utilities.ConvertToDefiniteLength(stream.ToArray(), password.ToCharArray());
var convertedCertificate =
new X509Certificate2(
pfx, password,
X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
return convertedCertificate;
}
}
Reader
public class CertificateExtensionReader
{
private readonly ILogger logger;
public CertificateExtensionReader(ILogger logger)
{
this.logger = logger;
}
public CertificateExtensionValues ReadExtensionValues(byte[] certificate)
{
var x509Certificate2 = new X509Certificate2(certificate);
var region = GetCustomExtensionValue(x509Certificate2.Extensions, new Oid("1.3.6.1.1.5.6.100.345434.345"));
return new CertificateExtensionValues { Region = region };
}
private string GetCustomExtensionValue(X509ExtensionCollection x509Extensions, Oid oId)
{
var extension = x509Extensions[oId.Value];
if(extension == null)
throw new CertificateExtensionValidationException($"The client certificate does not contain the expected extensions '{oId.FriendlyName}' with OID {oId.Value}.");
if (extension.RawData == null)
throw new CertificateExtensionValidationException($"Device client certificate does not a value for the '{oId.FriendlyName}' extension with OID {oId.Value}");
var customExtensionValue = Encoding.UTF8.GetString(extension.RawData).Trim();
logger.LogInformation($"Custom Extension value for the '{oId.FriendlyName}' extension with OID {oId.Value}: '{customExtensionValue}'");
return customExtensionValue;
}
}
public class CertificateExtensionValues
{
public string Region { get; set; }
}
Test
[TestFixture]
public class CertificateExtensionReaderFixture
{
private ILogger logger = new NullLogger<CertificateExtensionReaderFixture>();
private CertificateExtensionReader reader;
[SetUp]
public void Setup()
{
reader = new CertificateExtensionReader(logger);
}
[Test]
public void ShouldReadExtensionValues()
{
var certificate = CertificateGenerator.GenerateCertificate("r1").Export(X509ContentType.Pfx);
var values = reader.ReadExtensionValues(certificate);
values.Region.Should().Be("r1");
}
}
Expected values.Region to be "r1" with a length of 2, but "\u0004\u0002r1" has a length of 4, differs near "\u0004\u0002r" (index 0).
So BouncyCastle added two extra bytes \u0004\u0002 (End of transmission, begin of text) for the extension value.
I saved the certificate to a file and dumped it via certutil -dump -v test.pfx
What am I doing wrong? Is it the generation of the certificate? Or is is how I read the values? Are all extension values encoded this way? I was expecting only the string bytes. I could not find something in the spec.
What am I doing wrong?
you are creating extension wrong.
certificateGenerator.AddExtension(new DerObjectIdentifier(fakeOid), false, Encoding.ASCII.GetBytes(region));
Last parameter is incorrect. It must contain any valid ASN.1 type. Since parameter value is invalid ASN.1 type, BC assumes it is just a random/arbitrary octet string and implicitly encodes raw value into ASN.1 OCTET_STRING type. If it is supposed to be a text string, then use any of applicable ASN.1 string types that fits you requirements and char set.
And you have to update your reader to expect ASN.1 string type you chose for encoding and then decode string value from ASN.1 string type.

iTextSharp 5 - Upgrading SHA1 signing to SHA256

I am currently having an issue with converting our PDF signing from SHA1 hashing to SHA256.
We use the Digest signing approach and not signing the entire PDF.
I have gone through a number of articles and have got to the point where the digest comes back in SHA256, however this invalidates the signature (which was previously valid).
The validity error message is "The document has been altered or corrupted since the Signature was applied"
I am new to Digital Signatures, and as a result would appreciate any assistance in this regard. If any of my terminology is incorrect, please feel free to correct me.
public static byte[] Sign(PdfSignatureAppearance signatureAppearance, IExternalSignature externalSignature, ICollection<X509Certificate> chain, ICollection<ICrlClient> crlList, IOcspClient ocspClient, ITSAClient tsaClient, int estimatedSize, CryptoStandard sigtype, PdfDictionary customSignatureProperties)
{
byte[] signedCMS = null;
List<X509Certificate> certificateChain = new List<X509Certificate>(chain);
ICollection<byte[]> crlBytes = BuildCrlBytes(certificateChain, crlList);
if (estimatedSize == 0)
{
estimatedSize = CalculateEstimatedSizeOfSignature(crlBytes, ocspClient, tsaClient);
}
signatureAppearance.CryptoDictionary = CreateCryptoDictionary(signatureAppearance, customSignatureProperties, PdfName.ADOBE_PPKLITE, PdfName.ADBE_PKCS7_DETACHED);
signatureAppearance.PreClose(SignGetEstimatedSizeDictionary(estimatedSize));
using (Stream data = signatureAppearance.GetRangeStream())
{
byte[] documentHash = DigestAlgorithms.Digest(data, externalSignature.GetHashAlgorithm());
signedCMS = externalSignature.Sign(documentHash);
byte[] encodedSignatureWithTimestamp = SignAddTimestampToSignedCms(tsaClient, signedCMS);
if (SignCheckEstimatedSize(encodedSignatureWithTimestamp, estimatedSize))
{
byte[] paddedSignature = new byte[estimatedSize];
Array.Copy(encodedSignatureWithTimestamp, 0, paddedSignature, 0, encodedSignatureWithTimestamp.Length);
signatureAppearance.Close(SignGetDigestDictionary(paddedSignature));
}
}
return signedCMS;
}
private static PdfDictionary CreateCryptoDictionary(PdfSignatureAppearance signatureAppearance, PdfDictionary customSignatureProperties, PdfName filter, PdfName subFilter)
{
PdfSignature dictionary = new PdfSignature(filter, subFilter);
dictionary.Reason = signatureAppearance.Reason;
dictionary.Location = signatureAppearance.Location;
dictionary.Contact = signatureAppearance.Contact;
dictionary.Date = new PdfDate(signatureAppearance.SignDate);
if (customSignatureProperties != null)
{
dictionary.PutAll(customSignatureProperties);
}
return dictionary;
}
private static byte[] SignAddTimestampToSignedCms(ITSAClient tsaClient, byte[] signedCms)
{
ArrayList newSigners = new ArrayList();
CmsSignedData cmsSignedData = new CmsSignedData(signedCms);
foreach (SignerInformation information in cmsSignedData.GetSignerInfos().GetSigners())
{
byte[] signedDigest = information.GetSignature();
byte[] timestampImprint = DigestAlgorithms.Digest(tsaClient.GetMessageDigest(), signedDigest);
byte[] timestampToken = tsaClient.GetTimeStampToken(timestampImprint);
newSigners.Add(SignerInformation.ReplaceUnsignedAttributes(information, GenerateUnsignedAttributes(Constants.TIMESTAMP_OID, timestampToken)));
}
return CmsSignedData.ReplaceSigners(cmsSignedData, new SignerInformationStore(newSigners)).GetEncoded();
}
Edit: Adding in the code implemented by the IExternalSignature Sign method implementation
public static byte[] SignDigest(string signer, byte[] digest)
{
SignatureData data = ConfigurationHelpers.GetSignatureData(signer);
var cryptoServiceProvider = RSACryptoServiceProvider)data.SigningCertificate.PrivateKey; // verify private key access
var contentInfo = new ContentInfo(digest);
var signedCms = new SignedCms(contentInfo);
var cmsSigner = new CmsSigner(data.SigningCertificate);
cmsSigner.IncludeOption = X509IncludeOption.WholeChain;
signedCms.ComputeSignature(cmsSigner);
// Encode the CMS/PKCS #7 message.
return signedCms.Encode();
}

How to get RSACryptoServiceProvider public and private key only in c#

I am running below code to get public and private key only, but it seems it outputs the whole XML format. I only need to output the keys as shown in Public and Private Key demo
static RSACryptoServiceProvider rsa;
private RSAParameters _privateKey;
private RSAParameters _publicKey;
public RSACrypto()
{
rsa = new RSACryptoServiceProvider(2048);
_privateKey = rsa.ExportParameters(true);
_publicKey = rsa.ExportParameters(false);
}
public string GetPublicKeyString()
{
var sw = new StringWriter();
var xs = new XmlSerializer(typeof(RSAParameters));
xs.Serialize(sw, _publicKey);
return sw.ToString();
}
public string GetPrivateKeyString()
{
var sw = new StringWriter();
var xs = new XmlSerializer(typeof(RSAParameters));
xs.Serialize(sw, _privateKey);
return sw.ToString();
}
Starting in .NET Core 3.0, this is (largely) built-in.
Writing SubjectPublicKeyInfo and RSAPrivateKey
.NET Core 3.0 built-in API
The output of the builtin API is the binary representation, to make them PEM you need to output the header, footer, and base64:
private static string MakePem(byte[] ber, string header)
{
StringBuilder builder = new StringBuilder("-----BEGIN ");
builder.Append(header);
builder.AppendLine("-----");
string base64 = Convert.ToBase64String(ber);
int offset = 0;
const int LineLength = 64;
while (offset < base64.Length)
{
int lineEnd = Math.Min(offset + LineLength, base64.Length);
builder.AppendLine(base64.Substring(offset, lineEnd - offset));
offset = lineEnd;
}
builder.Append("-----END ");
builder.Append(header);
builder.AppendLine("-----");
return builder.ToString();
}
So to produce the strings:
string publicKey = MakePem(rsa.ExportSubjectPublicKeyInfo(), "PUBLIC KEY");
string privateKey = MakePem(rsa.ExportRSAPrivateKey(), "RSA PRIVATE KEY");
Semi-manually
If you can't use .NET Core 3.0, but you can use pre-release NuGet packages, you can make use of the prototype ASN.1 writer package (which is the same code that's used internally in .NET Core 3.0; it's just that the API surface isn't finalized).
To make the public key:
private static string ToSubjectPublicKeyInfo(RSA rsa)
{
RSAParameters rsaParameters = rsa.ExportParameters(false);
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
writer.PushSequence();
writer.PushSequence();
writer.WriteObjectIdentifier("1.2.840.113549.1.1.1");
writer.WriteNull();
writer.PopSequence();
AsnWriter innerWriter = new AsnWriter(AsnEncodingRules.DER);
innerWriter.PushSequence();
WriteRSAParameter(innerWriter, rsaParameters.Modulus);
WriteRSAParameter(innerWriter, rsaParameters.Exponent);
innerWriter.PopSequence();
writer.WriteBitString(innerWriter.Encode());
writer.PopSequence();
return MakePem(writer.Encode(), "PUBLIC KEY");
}
And to make the private key:
private static string ToRSAPrivateKey(RSA rsa)
{
RSAParameters rsaParameters = rsa.ExportParameters(true);
AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
writer.PushSequence();
writer.WriteInteger(0);
WriteRSAParameter(writer, rsaParameters.Modulus);
WriteRSAParameter(writer, rsaParameters.Exponent);
WriteRSAParameter(writer, rsaParameters.D);
WriteRSAParameter(writer, rsaParameters.P);
WriteRSAParameter(writer, rsaParameters.Q);
WriteRSAParameter(writer, rsaParameters.DP);
WriteRSAParameter(writer, rsaParameters.DQ);
WriteRSAParameter(writer, rsaParameters.InverseQ);
writer.PopSequence();
return MakePem(writer.Encode(), "RSA PRIVATE KEY");
}
Reading them back
.NET Core 3.0 built-in API
Except that .NET Core 3.0 doesn't understand PEM encoding, so you have to do PEM->binary yourself:
private const string RsaPrivateKey = "RSA PRIVATE KEY";
private const string SubjectPublicKeyInfo = "PUBLIC KEY";
private static byte[] PemToBer(string pem, string header)
{
// Technically these should include a newline at the end,
// and either newline-or-beginning-of-data at the beginning.
string begin = $"-----BEGIN {header}-----";
string end = $"-----END {header}-----";
int beginIdx = pem.IndexOf(begin);
int base64Start = beginIdx + begin.Length;
int endIdx = pem.IndexOf(end, base64Start);
return Convert.FromBase64String(pem.Substring(base64Start, endIdx - base64Start));
}
Once that's done you can now load the keys:
using (RSA rsa = RSA.Create())
{
rsa.ImportRSAPrivateKey(PemToBer(pemPrivateKey, RsaPrivateKey), out _);
...
}
using (RSA rsa = RSA.Create())
{
rsa.ImportSubjectPublicKeyInfo(PemToBer(pemPublicKey, SubjectPublicKeyInfo), out _);
...
}
Semi-manually
If you can't use .NET Core 3.0, but you can use pre-release NuGet packages, you can make use of the prototype ASN.1 reader package (which is the same code that's used internally in .NET Core 3.0; it's just that the API surface isn't finalized).
For the public key:
private static RSA FromSubjectPublicKeyInfo(string pem)
{
AsnReader reader = new AsnReader(PemToBer(pem, SubjectPublicKeyInfo), AsnEncodingRules.DER);
AsnReader spki = reader.ReadSequence();
reader.ThrowIfNotEmpty();
AsnReader algorithmId = spki.ReadSequence();
if (algorithmId.ReadObjectIdentifierAsString() != "1.2.840.113549.1.1.1")
{
throw new InvalidOperationException();
}
algorithmId.ReadNull();
algorithmId.ThrowIfNotEmpty();
AsnReader rsaPublicKey = spki.ReadSequence();
RSAParameters rsaParameters = new RSAParameters
{
Modulus = ReadNormalizedInteger(rsaPublicKey),
Exponent = ReadNormalizedInteger(rsaPublicKey),
};
rsaPublicKey.ThrowIfNotEmpty();
RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParameters);
return rsa;
}
private static byte[] ReadNormalizedInteger(AsnReader reader)
{
ReadOnlyMemory<byte> memory = reader.ReadIntegerBytes();
ReadOnlySpan<byte> span = memory.Span;
if (span[0] == 0)
{
span = span.Slice(1);
}
return span.ToArray();
}
And because the private key values have to have the correct size arrays, the private key one is just a little trickier:
private static RSA FromRSAPrivateKey(string pem)
{
AsnReader reader = new AsnReader(PemToBer(pem, RsaPrivateKey), AsnEncodingRules.DER);
AsnReader rsaPrivateKey = reader.ReadSequence();
reader.ThrowIfNotEmpty();
if (!rsaPrivateKey.TryReadInt32(out int version) || version != 0)
{
throw new InvalidOperationException();
}
byte[] modulus = ReadNormalizedInteger(rsaPrivateKey);
int halfModulusLen = (modulus.Length + 1) / 2;
RSAParameters rsaParameters = new RSAParameters
{
Modulus = modulus,
Exponent = ReadNormalizedInteger(rsaPrivateKey),
D = ReadNormalizedInteger(rsaPrivateKey, modulus.Length),
P = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
Q = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
DP = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
DQ = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
InverseQ = ReadNormalizedInteger(rsaPrivateKey, halfModulusLen),
};
rsaPrivateKey.ThrowIfNotEmpty();
RSA rsa = RSA.Create();
rsa.ImportParameters(rsaParameters);
return rsa;
}
private static byte[] ReadNormalizedInteger(AsnReader reader, int length)
{
ReadOnlyMemory<byte> memory = reader.ReadIntegerBytes();
ReadOnlySpan<byte> span = memory.Span;
if (span[0] == 0)
{
span = span.Slice(1);
}
byte[] buf = new byte[length];
int skipSize = length - span.Length;
span.CopyTo(buf.AsSpan(skipSize));
return buf;
}
The Bouncycastle C# library has some helper classes that can make this relatively easy. It is not well documented unfortunately. Here is an example:
using System;
using System.IO;
using System.Security.Cryptography;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
namespace ExportToStandardFormats
{
class MainClass
{
public static void Main(string[] args)
{
var rsa = new RSACryptoServiceProvider(2048);
var rsaKeyPair = DotNetUtilities.GetRsaKeyPair(rsa);
var writer = new StringWriter();
var pemWriter = new PemWriter(writer);
pemWriter.WriteObject(rsaKeyPair.Public);
pemWriter.WriteObject(rsaKeyPair.Private);
Console.WriteLine(writer);
}
}
}
I wanted to extract public and private key as char array, and not as string. I found one solution which is a modification of answers provided by James and Vikram above. It could be helpful for someone looking for it.
public static void GenerateKeyPair()
{
char[] private_key= null;
char[] public_key=null;
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
var rsaKeyPair = DotNetUtilities.GetRsaKeyPair(rsa);
//PrivateKey
MemoryStream memoryStream = new MemoryStream();
TextWriter streamWriter = new StreamWriter(memoryStream);
PemWriter pemWriter = new PemWriter(streamWriter);
pemWriter.WriteObject(rsaKeyPair.Private);
streamWriter.Flush();
byte[] bytearray = memoryStream.GetBuffer();
private_key = Encoding.ASCII.GetChars(bytearray);
//PublicKey
memoryStream = new MemoryStream();
streamWriter = new StreamWriter(memoryStream);
pemWriter = new PemWriter(streamWriter);
pemWriter.WriteObject(rsaKeyPair.Public);
streamWriter.Flush();
bytearray = memoryStream.GetBuffer();
public_key = Encoding.ASCII.GetChars(bytearray);
}
Adding on to the above answer, to get public and private key separately in string format, you can use the following code snippet.
public static void GenerateKeyPair()
{
try
{
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048);
var rsaKeyPair = DotNetUtilities.GetRsaKeyPair(rsa);
//Getting publickey
TextWriter textWriter = new StringWriter();
PemWriter pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(rsaKeyPair.Public);
publicKey = textWriter.ToString();
//Getting privatekey
textWriter = new StringWriter();
pemWriter = new PemWriter(textWriter);
pemWriter.WriteObject(rsaKeyPair.Private);
privateKey = textWriter.ToString();
Console.WriteLine("public key, {0}", publicKey);
Console.WriteLine("private key, {0}", privateKey);
}
catch (Exception e)
{
Console.WriteLine($"GenerateKeyPair Failed with {e}");
Console.WriteLine(e);
}
}

Categories

Resources