Use Openssl Rsa Key With .Net

Use OpenSSL RSA key with .Net

I am using openssl 0.9.6g and I have created public/private keypair
using RSA_generate_key(). It gives me keys like:

-----BEGIN RSA PUBLIC KEY-----
...
-----END RSA PUBLIC KEY-----

I think what I am looking for is "how to convert rsa public key from pkcs#1 to x509 format.

Yeah, .Net can consume some ASN.1/DER encoded keys, and some PEM encoded keys. The difference is PKCS encoding versus Traditional encoding (OpenSSL calls it "Traditional"). The traditional encoding is the SubjectPublicKeyInfo and it includes the OID and the public key.

So you are looking for either an ASN.1/DER encoding or a PEM encoding that writes SubjectPublicKeyInfo, and not just the public key.


I have another module in .NET which throws an exception when passed in
this key due to its format. It takes format like:

-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----

In this case, use PEM_write_bio_PUBKEY rather than PEM_write_bio_RSAPublicKey.

PEM_write_bio_PUBKEY writes the SubjectPublicKeyInfo; while PEM_write_bio_RSAPublicKey writes only the public key.

You will need an EVP_PKEY, so use EVP_PKEY_set1_RSA to convert it.


This is a PKCS key in OpenSSL. Its just the public key. You would use PEM_write_RSAPublicKey to write it:

-----BEGIN RSA PUBLIC KEY-----

And this is a Traditional key in OpenSSL. Its the SubjectPublicKeyInfo, and it includes an OID for the algorithm (rsaEncryption) and the public key. You would use PEM_write_bio_PUBKEY to write it:

-----BEGIN PUBLIC KEY-----

Instead of saving the key with PEM_write_RSAPublicKey, you should write out the SubjectPublicKeyInfo structure in ASN.1/DER format with i2d_RSA_PUBKEY_bio; or write it out in PEM format with PEM_write_bio_PUBKEY.

The program below creates a RSA key pair, and then writes out the public key in all the formats. Be sure to save the private key, too.

(And I'm glad you have the C++ tag. unique_ptr makes this exercise so much easier).

#include <memory>
using std::unique_ptr;

#include <openssl/bn.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/x509.h>

#include <cassert>
#define ASSERT assert

using BN_ptr = std::unique_ptr<BIGNUM, decltype(&::BN_free)>;
using RSA_ptr = std::unique_ptr<RSA, decltype(&::RSA_free)>;
using EVP_KEY_ptr = std::unique_ptr<EVP_PKEY, decltype(&::EVP_PKEY_free)>;
using BIO_FILE_ptr = std::unique_ptr<BIO, decltype(&::BIO_free)>;

int main(int argc, char* argv[])
{
int rc;

RSA_ptr rsa(RSA_new(), ::RSA_free);
BN_ptr bn(BN_new(), ::BN_free);

BIO_FILE_ptr pem1(BIO_new_file("rsa-public-1.pem", "w"), ::BIO_free);
BIO_FILE_ptr pem2(BIO_new_file("rsa-public-2.pem", "w"), ::BIO_free);
BIO_FILE_ptr der1(BIO_new_file("rsa-public-1.der", "w"), ::BIO_free);
BIO_FILE_ptr der2(BIO_new_file("rsa-public-2.der", "w"), ::BIO_free);

rc = BN_set_word(bn.get(), RSA_F4);
ASSERT(rc == 1);

// Generate key
rc = RSA_generate_key_ex(rsa.get(), 2048, bn.get(), NULL);
ASSERT(rc == 1);

// Convert RSA key to PKEY
EVP_KEY_ptr pkey(EVP_PKEY_new(), ::EVP_PKEY_free);
rc = EVP_PKEY_set1_RSA(pkey.get(), rsa.get());
ASSERT(rc == 1);

//////////

// Write just the public key in ASN.1/DER
// Load with d2i_RSAPublicKey_bio
rc = i2d_RSAPublicKey_bio(der1.get(), rsa.get());
ASSERT(rc == 1);

// Write just the public key in PEM
// Load with PEM_read_bio_RSAPublicKey
rc = PEM_write_bio_RSAPublicKey(pem1.get(), rsa.get());
ASSERT(rc == 1);

// Write SubjectPublicKeyInfo with OID and public key in ASN.1/DER
// Load with d2i_RSA_PUBKEY_bio
rc = i2d_RSA_PUBKEY_bio(der2.get(), rsa.get());
ASSERT(rc == 1);

// Write SubjectPublicKeyInfo with OID and public key in PEM
// Load with PEM_read_bio_PUBKEY
rc = PEM_write_bio_PUBKEY(pem2.get(), pkey.get());
ASSERT(rc == 1);

return 0;
}

The set1 in EVP_PKEY_set1_RSA bumps the reference count, so you don't get a segfault on a double free.

After executing the program, you get the expected PEM and ASN.1/DER:

$ cat rsa-public-1.pem 
-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEA0cgFv6wEcqoOhPtHdVmX4YFlCwodnSqooeCxFF1XadTS4sZkVJTC
kszHmRqXiXL2NmqnuDQsq6nLd+sNoU5yJJ+W1hwo7UToCyJ/81tS4n6mXvF8oilP
8YudD5QnBdW9LhqttBIN4Gk+Cxun+HG1rSJLGP9yiPPFd7DPiFz0Gd+juyWznWnP
gapDIWEKqANKma3j6b9eopBDWB0XAgU0HQ71MSNbcsPvDd23Ftx0re/7jG53V7Bn
eBy7fQsPmxcn4c74Lz4CvhOr7VdQpeBzNeG2CtkefKWyTk7Vu4FZnAgNd/202XAr
c6GmEQqD2M2zXH/nVZg5oLznECDVQ1x/pwIDAQAB
-----END RSA PUBLIC KEY-----

$ cat rsa-public-2.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0cgFv6wEcqoOhPtHdVmX
4YFlCwodnSqooeCxFF1XadTS4sZkVJTCkszHmRqXiXL2NmqnuDQsq6nLd+sNoU5y
JJ+W1hwo7UToCyJ/81tS4n6mXvF8oilP8YudD5QnBdW9LhqttBIN4Gk+Cxun+HG1
rSJLGP9yiPPFd7DPiFz0Gd+juyWznWnPgapDIWEKqANKma3j6b9eopBDWB0XAgU0
HQ71MSNbcsPvDd23Ftx0re/7jG53V7BneBy7fQsPmxcn4c74Lz4CvhOr7VdQpeBz
NeG2CtkefKWyTk7Vu4FZnAgNd/202XArc6GmEQqD2M2zXH/nVZg5oLznECDVQ1x/
pwIDAQAB
-----END PUBLIC KEY-----

$ dumpasn1 rsa-public-1.der
0 266: SEQUENCE {
4 257: INTEGER
: 00 D1 C8 05 BF AC 04 72 AA 0E 84 FB 47 75 59 97
: E1 81 65 0B 0A 1D 9D 2A A8 A1 E0 B1 14 5D 57 69
: D4 D2 E2 C6 64 54 94 C2 92 CC C7 99 1A 97 89 72
: F6 36 6A A7 B8 34 2C AB A9 CB 77 EB 0D A1 4E 72
: 24 9F 96 D6 1C 28 ED 44 E8 0B 22 7F F3 5B 52 E2
: 7E A6 5E F1 7C A2 29 4F F1 8B 9D 0F 94 27 05 D5
: BD 2E 1A AD B4 12 0D E0 69 3E 0B 1B A7 F8 71 B5
: AD 22 4B 18 FF 72 88 F3 C5 77 B0 CF 88 5C F4 19
: [ Another 129 bytes skipped ]
265 3: INTEGER 65537
: }

0 warnings, 0 errors.

$ dumpasn1 rsa-public-2.der
0 290: SEQUENCE {
4 13: SEQUENCE {
6 9: OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
17 0: NULL
: }
19 271: BIT STRING, encapsulates {
24 266: SEQUENCE {
28 257: INTEGER
: 00 D1 C8 05 BF AC 04 72 AA 0E 84 FB 47 75 59 97
: E1 81 65 0B 0A 1D 9D 2A A8 A1 E0 B1 14 5D 57 69
: D4 D2 E2 C6 64 54 94 C2 92 CC C7 99 1A 97 89 72
: F6 36 6A A7 B8 34 2C AB A9 CB 77 EB 0D A1 4E 72
: 24 9F 96 D6 1C 28 ED 44 E8 0B 22 7F F3 5B 52 E2
: 7E A6 5E F1 7C A2 29 4F F1 8B 9D 0F 94 27 05 D5
: BD 2E 1A AD B4 12 0D E0 69 3E 0B 1B A7 F8 71 B5
: AD 22 4B 18 FF 72 88 F3 C5 77 B0 CF 88 5C F4 19
: [ Another 129 bytes skipped ]
289 3: INTEGER 65537
: }
: }
: }

0 warnings, 0 errors.

Related, see How to generate RSA private key using openssl?. It shows you how to write a RSA public and private key in a number of formats.

How do I import an RSA Public Key from .NET into OpenSSL

In the .NET program create a new RSACryptoServiceProvider. Export the public key as RSAParameters and write the Modulus and Exponent values to disk. Like this:

RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(4096); //4096 bit key
RSAParameters par = rsa.ExportParameters(false); // export the public key

File.WriteAllBytes(@"C:\modulus.bin", par.Modulus); // write the modulus and the exponent to disk
File.WriteAllBytes(@"C:\exponent.bin", par.Exponent);

On the C++ side you'll need to read the modulus and exponent values from disk convert them into BIGNUM values. These values will be loaded into an RSA key and then you can encrypt the plain text and write the cipher text to disk. Like this:

RSA * key;

unsigned char *modulus;
unsigned char *exp;

FILE * fp = fopen("c:\\modulus.bin", "rb"); // Read the modulus from disk
modulus = new unsigned char[512];
memset(modulus, 0, 512);
fread(modulus, 512, 1, fp);
fclose(fp);

fp = fopen("c:\\exponent.bin", "rb"); // Read the exponent from disk
exp = new unsigned char[3];
memset(exp, 0, 3);
fread(exp, 3, 1, fp);
fclose(fp);

BIGNUM * bn_mod = NULL;
BIGNUM * bn_exp = NULL;

bn_mod = BN_bin2bn(modulus, 512, NULL); // Convert both values to BIGNUM
bn_exp = BN_bin2bn(exp, 3, NULL);

key = RSA_new(); // Create a new RSA key
key->n = bn_mod; // Assign in the values
key->e = bn_exp;
key->d = NULL;
key->p = NULL;
key->q = NULL;

int maxSize = RSA_size(key); // Find the length of the cipher text

cipher = new char[valid];
memset(cipher, 0, valid);
RSA_public_encrypt(strlen(plain), plain, cipher, key, RSA_PKCS1_PADDING); // Encrypt plaintext

fp = fopen("C:\\cipher.bin", "wb"); // write ciphertext to disk
fwrite(cipher, 512, 1, fp);
fclose(fp);

Finally you can take the ciphertext and decrypt it in C# without any difficulty.

byte[] cipher = File.ReadAllBytes(@"c:\cipher.bin"); // Read ciphertext from file
byte[] plain = rsa.Decrypt(cipher, false); // Decrypt ciphertext

Console.WriteLine(ASCIIEncoding.ASCII.GetString(plain)); // Decode and display plain text

Decrypting RSA using OpenSSL.NET with Existing Key

I ended up figuring it out through the object browser on the Managed Wrapper for OpenSSL.NET. This works:

    public static byte[] AsymmetricEncrypt(string publicKeyAsPem, byte[] payload)
{
CryptoKey d = CryptoKey.FromPublicKey(publicKeyAsPem, null);
RSA rsa = d.GetRSA();
byte[] result = rsa.PublicEncrypt(payload, RSA.Padding.PKCS1);
rsa.Dispose();
return result;
}

public static byte[] AsymmetricDecrypt(string privateKeyAsPem, byte[] payload)
{
CryptoKey d = CryptoKey.FromPrivateKey(privateKeyAsPem, null);
RSA rsa = d.GetRSA();
byte[] result = rsa.PrivateDecrypt(payload, RSA.Padding.PKCS1);
rsa.Dispose();
return result;
}

Generating keys for RSA dotnet core

You can convert the posted X.509/SPKI key to a PKCS#1 public key using the following OpenSSL statement:

openssl rsa -pubin -RSAPublicKey_out -in name_of_public_key.pem > name_of_public_key_conv_pkcs1.pem

This returns the following key for name_of_public_key_conv_pkcs1.pem:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAtZL7iKRPSxrCflER6j/IwB9fODXJgfxR4UBSU3oUJ8tIaBBnDrcu
tfXDfc7lZ9HcCZccvUsMzFKGJuvHthCE/LNJmZtRRd02aLynoZSWqDBerCdRqXHb
ecMfK8KPxQSsWfinNiyFG76vTX2+V8P6t4Cu8bM8j7foSBgOmECCSOjTuCG4bvKV
S3bnu2lSBNgCjEMltk9W/3oSzKbN/mwnGfViaXU5a1Zps3jLbx/z58o3Sb25QfQK
U4xeohcx+Wj6d14lI80RErS1QTqSQ1rz10Cs/Q1MudWstckqyE/u048GtXzQCzQO
e4hWlyrcFqfiEAbV2jPLU61oer4/wT+07QIDAQAB
-----END RSA PUBLIC KEY-----

Alternatively, you can generate a PKCS#1 public key directly using the following OpenSSL statements:

openssl genrsa -out name_of_private_key.pem 2048
openssl rsa -in name_of_private_key.pem -RSAPublicKey_out > name_of_public_key_pkcs1.pem

Public keys in PKCS#1 format can be imported with the code you posted, e.g.

var PublicKey = @"-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAtZL7iKRPSxrCflER6j/IwB9fODXJgfxR4UBSU3oUJ8tIaBBnDrcu
tfXDfc7lZ9HcCZccvUsMzFKGJuvHthCE/LNJmZtRRd02aLynoZSWqDBerCdRqXHb
ecMfK8KPxQSsWfinNiyFG76vTX2+V8P6t4Cu8bM8j7foSBgOmECCSOjTuCG4bvKV
S3bnu2lSBNgCjEMltk9W/3oSzKbN/mwnGfViaXU5a1Zps3jLbx/z58o3Sb25QfQK
U4xeohcx+Wj6d14lI80RErS1QTqSQ1rz10Cs/Q1MudWstckqyE/u048GtXzQCzQO
e4hWlyrcFqfiEAbV2jPLU61oer4/wT+07QIDAQAB
-----END RSA PUBLIC KEY-----".
Replace("-----BEGIN RSA PUBLIC KEY-----", "").
Replace("-----END RSA PUBLIC KEY-----", "").
Replace("\r\n", "");
var rsa = RSA.Create();
rsa.ImportRSAPublicKey(Convert.FromBase64String(PublicKey), out _);

RSAParameters parameters = rsa.ExportParameters(false);
Console.WriteLine(new BigInteger(parameters.Exponent, true, true)); // 65537
Console.WriteLine(new BigInteger(parameters.Modulus, true, true)); // 22921612997464368147681940553984745387167552018036344531503795467063837226615581953768444015539628345845035732103113334279875993301411098168640007990192163617624452836576802897196284289413557038039593995983320236405640276117810563150914793233680115042600127677172037054986051882799772185194759951925398974095268701931531156047608941244890064857847352301510189736406400522269201574332107656671336685945934753045233371160604896169352804846566979618872110365310073347596127824815830796826711019699206801083371733500629381548849681219453339114997443300562712444634750316194264179142382642144192449752430619501209065600237

C# OpenSSL RSA Private Key Encryption

I've finally resolved the issue. For anyone else thats interested the Least Significant byte of the datablock must be of value 0x00. By enforcing this condition RSA encryption and decryption works like a charm.

.NET RSA encryption not working with OpenSSL

Ended up going with bouncy castle implementation, though as Maarten points out there is likely a vanilla solution available.

Converting Openssl signing to .NET6

Why generally the native .NET methods cannot be used

For RSASignaturePadding.Pkcs1, the native .NET implementations SignData() and SignHash() follow the RSASSA-PKCS1-v1_5 signature scheme described in RFC8017, which applies EMSA-PKCS1-v1_5 as encoding operation: The message is hashed and the following value is signed (i.e. encrypted with the private key):

EM = 0x00 || 0x01 || PS || 0x00 || T

Here PS consists of so many 0xff values that the size of EM is equal to the size of the modulus of the key. T is the DER encoding of the DigestInfo value, which contains the digest OID and the hash, e.g. for SHA256:

3031300d060960864801650304020105000420 || H 

where H is the 32 bytes SHA56 hash of the message M to be signed.

In contrast, openssl rsautl uses the RSA algorithm directly, as mentioned in the NOTES section, i.e. the following data is signed:

EM' = 0x00 || 0x01 || PS || 0x00 || M

This cannot be achieved with the native .NET methods in general (except for a special use case, see below): SignData() hashes and therefore fails, SignHash() does not hash but internally (like SignData()) generates the DER encoding of the DigestInfo value.

An alternative is BouncyCastle, which signs with the algorithm NoneWithRSA just like openssl rsautl.

One disadvantage of this algorithm is that only short messages can be signed due to the missing hashing, since the length criterion cannot be fulfilled for longer messages (according to which the size of EM' must correspond to the size of the modulus of the key).

Key Import

The posted key is a PEM encoded private key in PKCS#1 format.

.NET supports the import of PEM encoded keys (private/public, PKCS#8/PKCS#1 format) with ImportFromPem() since .NET 5, but the import of DER encoded keys has been supported since .NET Core 3.0. A private DER encoded key in PKCS#1 format can be imported with ImportRSAPrivateKey() (the conversion between PEM and DER encoding is trivial and consists of removing header, footer and line breaks and Base64 decoding of the remaining body).

BouncyCastle supports the import of a PEM encoded key with the PemReader class.

A possible implementation of the posted OpenSSL functionality with BouncyCastle

The following code generates the same signature as the OpenSSL statement when rasi.bin holds the data from dataToSign and riktest.key holds the key from privatePkcs1Pem:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using System;
using System.IO;

...

// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] dataToSign = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");

// Import private PKCS#1 key, PEM encoded
PemReader pemReader = new PemReader(new StringReader(privatePkcs1Pem));
AsymmetricKeyParameter privateKeyParameter = ((AsymmetricCipherKeyPair)pemReader.ReadObject()).Private;

// Sign raw data
ISigner signer = SignerUtilities.GetSigner("NoneWithRSA");
signer.Init(true, privateKeyParameter);
signer.BlockUpdate(dataToSign, 0, dataToSign.Length);
byte[] signature = signer.GenerateSignature();

Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C

Special use case - when the native C# methods can be used

If rasi.bin contains the DER encoding of the DigestInfo value, BouncyCastle is not needed.

The following example assumes that rasi.bin contains the DER encoding of the DigestInfo value for the message The quick brown fox jumps over the lazy dog with SHA256 as digest. I.e. the last 32 bytes correspond to the SHA256 hash.

A possible implementation with native .NET methods is then:

using System;
using System.Security.Cryptography;

...

// For testing purposes a 512 bits key is used.
// In practice, keys >= 2048 bits must be used for security reasons!
string privatePkcs1Pem = @"-----BEGIN RSA PRIVATE KEY-----
MIIBOwIBAAJBANoHbFSEZoOSB9Kxt7t8PoBwmauaODjECHqJgtTU3h4MW5K3857+
04Flc6x6a9xxyvCKS5RtOP2gaOlOVtrph0ECAwEAAQJBALu8LpRr2RWrdV7/tfQT
HIJd8oQnbAe9DIvuwh/fF08IwApOE/iGL+Ded49eoHHu1OXycZhpHavN/sQMnssP
FNECIQDyDIW7V5UUu16ZAeupeQ7zdV6ykVngd0bb3FEn99EchQIhAOaYe3ll211q
SIXVjKHudMn3xe6Vvguc9O7cwCB+gyqNAiEAsr3kk6/de23SMZNlf8TR8Z8eyybj
BAuQ3BMaKzWpyjECIFMR0UFNYTYIyLF12aCoH2h2mtY1GW5jj5TQ72GFUcktAiAf
WWXnts7m8kZWuKjfD0MQiW+w4iAph+51j+wiL3EMAQ==
-----END RSA PRIVATE KEY-----";
byte[] sha256DigestInfoDer = Convert.FromHexString("3031300d060960864801650304020105000420d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592");
byte[] sha256HashToSign = new byte[32];
Buffer.BlockCopy(sha256DigestInfoDer, sha256DigestInfoDer.Length - sha256HashToSign.Length, sha256HashToSign, 0, sha256HashToSign.Length);

using (RSA rsa = RSA.Create())
{
rsa.ImportFromPem(privatePkcs1Pem);
byte[] signature = rsa.SignHash(sha256HashToSign, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); // pass the SHA256 hash, internally the DER encoding of the DigestInfo is generated (which is why the digest must be specified)
Console.WriteLine(Convert.ToHexString(signature)); // 8C83CAD897EDA249FEC9EBA231061D585DAFC99177267E3E71BB8A3FCE07CC6663BF4DF7AF2E1C1945D2A6BB42EB25F042228B591FC18CDA82D92CAAE844670C
}

which gives the same signature, since rasi.bin is identical in both cases.

However, keep in mind that the last approach only works if rasi.bin contains the DER encoding of the DigestInfo value, while the first solution works for arbitrary data in rasi.bin (as long as the length criterion is met).

C# RSA Public Key Output Not Correct

Unfortunately, the code in the answer you referenced isn't really correct - it exports a private key PEM format, but with only the public key fields correctly set, this is not the same as exporting an RSA public key in standard format.

I actually wrote the code in the other answer to that question, and at the time wrote a mode for exporting the public key in the standard format, but didn't include it in that answer as it wasn't required. Here it is:

private static void ExportPublicKey(RSACryptoServiceProvider csp, TextWriter outputStream)
{
var parameters = csp.ExportParameters(false);
using (var stream = new MemoryStream())
{
var writer = new BinaryWriter(stream);
writer.Write((byte)0x30); // SEQUENCE
using (var innerStream = new MemoryStream())
{
var innerWriter = new BinaryWriter(innerStream);
innerWriter.Write((byte)0x30); // SEQUENCE
EncodeLength(innerWriter, 13);
innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER
var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
EncodeLength(innerWriter, rsaEncryptionOid.Length);
innerWriter.Write(rsaEncryptionOid);
innerWriter.Write((byte)0x05); // NULL
EncodeLength(innerWriter, 0);
innerWriter.Write((byte)0x03); // BIT STRING
using (var bitStringStream = new MemoryStream())
{
var bitStringWriter = new BinaryWriter(bitStringStream);
bitStringWriter.Write((byte)0x00); // # of unused bits
bitStringWriter.Write((byte)0x30); // SEQUENCE
using (var paramsStream = new MemoryStream())
{
var paramsWriter = new BinaryWriter(paramsStream);
EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus
EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent
var paramsLength = (int)paramsStream.Length;
EncodeLength(bitStringWriter, paramsLength);
bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength);
}
var bitStringLength = (int)bitStringStream.Length;
EncodeLength(innerWriter, bitStringLength);
innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength);
}
var length = (int)innerStream.Length;
EncodeLength(writer, length);
writer.Write(innerStream.GetBuffer(), 0, length);
}

var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray();
outputStream.WriteLine("-----BEGIN PUBLIC KEY-----");
for (var i = 0; i < base64.Length; i += 64)
{
outputStream.WriteLine(base64, i, Math.Min(64, base64.Length - i));
}
outputStream.WriteLine("-----END PUBLIC KEY-----");
}
}

private static void EncodeLength(BinaryWriter stream, int length)
{
if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative");
if (length < 0x80)
{
// Short form
stream.Write((byte)length);
}
else
{
// Long form
var temp = length;
var bytesRequired = 0;
while (temp > 0)
{
temp >>= 8;
bytesRequired++;
}
stream.Write((byte)(bytesRequired | 0x80));
for (var i = bytesRequired - 1; i >= 0; i--)
{
stream.Write((byte)(length >> (8 * i) & 0xff));
}
}
}

private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true)
{
stream.Write((byte)0x02); // INTEGER
var prefixZeros = 0;
for (var i = 0; i < value.Length; i++)
{
if (value[i] != 0) break;
prefixZeros++;
}
if (value.Length - prefixZeros == 0)
{
EncodeLength(stream, 1);
stream.Write((byte)0);
}
else
{
if (forceUnsigned && value[prefixZeros] > 0x7f)
{
// Add a prefix zero to force unsigned if the MSB is 1
EncodeLength(stream, value.Length - prefixZeros + 1);
stream.Write((byte)0);
}
else
{
EncodeLength(stream, value.Length - prefixZeros);
}
for (var i = prefixZeros; i < value.Length; i++)
{
stream.Write(value[i]);
}
}
}


Related Topics



Leave a reply



Submit