1
//**********************************************************************************
4
// .NET 2.0 OpenSSL Public & Private Key Parser
6
// Copyright (C) 2008 JavaScience Consulting
8
//***********************************************************************************
13
// (1) OpenSSL PEM or DER public keys
14
// (2) OpenSSL PEM or DER traditional SSLeay private keys (encrypted and unencrypted)
15
// (3) PKCS #8 PEM or DER encoded private keys (encrypted and unencrypted)
16
// Keys in PEM format must have headers/footers .
17
// Encrypted Private Key in SSLEay format not supported in DER
18
// Removes header/footer lines.
19
// For traditional SSLEAY PEM private keys, checks for encrypted format and
20
// uses PBE to extract 3DES key.
21
// For SSLEAY format, only supports encryption format: DES-EDE3-CBC
22
// For PKCS #8, only supports PKCS#5 v2.0 3des.
23
// Parses private and public key components and returns .NET RSA object.
24
// Creates dummy unsigned certificate linked to private keypair and
25
// optionally exports to pkcs #12
28
// http://www.openssl.org/docs/crypto/pem.html#PEM_ENCRYPTION_FORMAT
29
//**************************************************************************************
34
using System.Security.Cryptography;
35
using System.Security.Cryptography.X509Certificates;
36
using System.Runtime.InteropServices;
37
using System.Security;
38
using System.Diagnostics;
39
using System.ComponentModel;
42
namespace JavaScience {
46
[DllImport("crypt32.dll", SetLastError=true)]
47
public static extern IntPtr CertCreateSelfSignCertificate(
49
ref CERT_NAME_BLOB pSubjectIssuerBlob,
51
ref CRYPT_KEY_PROV_INFO pKeyProvInfo,
52
IntPtr pSignatureAlgorithm,
58
[DllImport("crypt32.dll", SetLastError=true)]
59
public static extern bool CertStrToName(
60
uint dwCertEncodingType,
64
[In, Out] byte[] pbEncoded,
68
[DllImport("crypt32.dll", SetLastError=true)]
69
public static extern bool CertFreeCertificateContext(
75
[StructLayout(LayoutKind.Sequential)]
76
public struct CRYPT_KEY_PROV_INFO
78
[MarshalAs(UnmanagedType.LPWStr)] public String pwszContainerName;
79
[MarshalAs(UnmanagedType.LPWStr)] public String pwszProvName;
80
public uint dwProvType;
82
public uint cProvParam;
83
public IntPtr rgProvParam;
84
public uint dwKeySpec;
87
[StructLayout(LayoutKind.Sequential)]
88
public struct CERT_NAME_BLOB
96
public class opensslkey {
98
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----" ;
99
const String pemprivfooter = "-----END RSA PRIVATE KEY-----" ;
100
const String pempubheader = "-----BEGIN PUBLIC KEY-----" ;
101
const String pempubfooter = "-----END PUBLIC KEY-----" ;
102
const String pemp8header = "-----BEGIN PRIVATE KEY-----" ;
103
const String pemp8footer = "-----END PRIVATE KEY-----" ;
104
const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----" ;
105
const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----" ;
107
// static byte[] pempublickey;
108
// static byte[] pemprivatekey;
109
// static byte[] pkcs8privatekey;
110
// static byte[] pkcs8encprivatekey;
112
static bool verbose = false;
114
public static void Main(String[] args) {
117
if(args[0].ToUpper() == "V")
120
Console.ForegroundColor = ConsoleColor.Gray;
121
Console.Write("\nRSA public, private or PKCS #8 key file to decode: ");
122
String filename = Console.ReadLine().Trim();
123
if (filename == "") //exit while(true) loop
125
if (!File.Exists(filename)) {
126
Console.WriteLine("File \"{0}\" does not exist!\n", filename);
130
StreamReader sr = File.OpenText(filename);
131
String pemstr = sr.ReadToEnd().Trim();
133
if(pemstr.StartsWith("-----BEGIN"))
134
DecodePEMKey(pemstr);
136
DecodeDERKey(filename);
143
// ------- Decode PEM pubic, private or pkcs8 key ----------------
144
public static void DecodePEMKey(String pemstr)
147
byte[] pemprivatekey;
148
byte[] pkcs8privatekey;
149
byte[] pkcs8encprivatekey;
151
if(pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
153
Console.WriteLine("Trying to decode and parse a PEM public key ..");
154
pempublickey = DecodeOpenSSLPublicKey(pemstr);
155
if(pempublickey != null)
158
showBytes("\nRSA public key", pempublickey) ;
159
//PutFileBytes("rsapubkey.pem", pempublickey, pempublickey.Length) ;
160
RSACryptoServiceProvider rsa = DecodeX509PublicKey(pempublickey);
161
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
162
String xmlpublickey =rsa.ToXmlString(false) ;
163
Console.WriteLine("\nXML RSA public key: {0} bits\n{1}\n", rsa.KeySize, xmlpublickey) ;
170
else if(pemstr.StartsWith(pemprivheader) && pemstr.EndsWith(pemprivfooter))
172
Console.WriteLine("Trying to decrypt and parse a PEM private key ..");
173
pemprivatekey = DecodeOpenSSLPrivateKey(pemstr);
174
if(pemprivatekey != null)
177
showBytes("\nRSA private key", pemprivatekey) ;
178
//PutFileBytes("rsaprivkey.pem", pemprivatekey, pemprivatekey.Length) ;
179
RSACryptoServiceProvider rsa = DecodeRSAPrivateKey(pemprivatekey);
180
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
181
String xmlprivatekey =rsa.ToXmlString(true) ;
182
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
189
else if(pemstr.StartsWith(pemp8header) && pemstr.EndsWith(pemp8footer))
191
Console.WriteLine("Trying to decode and parse as PEM PKCS #8 PrivateKeyInfo ..");
192
pkcs8privatekey = DecodePkcs8PrivateKey(pemstr);
193
if(pkcs8privatekey != null)
196
showBytes("\nPKCS #8 PrivateKeyInfo", pkcs8privatekey) ;
197
//PutFileBytes("PrivateKeyInfo", pkcs8privatekey, pkcs8privatekey.Length) ;
198
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey);
201
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
202
String xmlprivatekey =rsa.ToXmlString(true) ;
203
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
207
Console.WriteLine("\nFailed to create an RSACryptoServiceProvider");
212
else if(pemstr.StartsWith(pemp8encheader) && pemstr.EndsWith(pemp8encfooter))
214
Console.WriteLine("Trying to decode and parse as PEM PKCS #8 EncryptedPrivateKeyInfo ..");
215
pkcs8encprivatekey = DecodePkcs8EncPrivateKey(pemstr);
216
if(pkcs8encprivatekey != null)
219
showBytes("\nPKCS #8 EncryptedPrivateKeyInfo", pkcs8encprivatekey) ;
220
//PutFileBytes("EncryptedPrivateKeyInfo", pkcs8encprivatekey, pkcs8encprivatekey.Length) ;
221
RSACryptoServiceProvider rsa = DecodeEncryptedPrivateKeyInfo(pkcs8encprivatekey);
224
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
225
String xmlprivatekey =rsa.ToXmlString(true) ;
226
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
230
Console.WriteLine("\nFailed to create an RSACryptoServiceProvider");
237
Console.WriteLine("Not a PEM public, private key or a PKCS #8");
246
// ------- Decode PEM pubic, private or pkcs8 key ----------------
247
public static void DecodeDERKey(String filename)
249
RSACryptoServiceProvider rsa = null ;
250
byte[] keyblob = GetFileBytes(filename);
254
rsa = DecodeX509PublicKey(keyblob);
257
Console.WriteLine("\nA valid SubjectPublicKeyInfo\n") ;
258
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
259
String xmlpublickey =rsa.ToXmlString(false) ;
260
Console.WriteLine("\nXML RSA public key: {0} bits\n{1}\n", rsa.KeySize, xmlpublickey) ;
264
rsa = DecodeRSAPrivateKey(keyblob);
267
Console.WriteLine("\nA valid RSAPrivateKey\n") ;
268
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
269
String xmlprivatekey =rsa.ToXmlString(true) ;
270
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
275
rsa = DecodePrivateKeyInfo(keyblob); //PKCS #8 unencrypted
278
Console.WriteLine("\nA valid PKCS #8 PrivateKeyInfo\n") ;
279
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
280
String xmlprivatekey =rsa.ToXmlString(true) ;
281
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
286
rsa = DecodeEncryptedPrivateKeyInfo(keyblob); //PKCS #8 encrypted
288
Console.WriteLine("\nA valid PKCS #8 EncryptedPrivateKeyInfo\n") ;
289
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
290
String xmlprivatekey =rsa.ToXmlString(true) ;
291
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
295
Console.WriteLine("Not a binary DER public, private or PKCS #8 key");
303
public static void ProcessRSA(RSACryptoServiceProvider rsa)
307
Console.Write("\n\nExport RSA private key to PKCS #12 file? (Y or N) ");
308
String resp = Console.ReadLine().ToUpper() ;
309
if(resp == "Y" || resp == "YES")
316
//-------- Generate pkcs #12 from an RSACryptoServiceProvider ---------
317
public static void RSAtoPKCS12(RSACryptoServiceProvider rsa)
319
CspKeyContainerInfo keyInfo = rsa.CspKeyContainerInfo;
320
String keycontainer = keyInfo.KeyContainerName;
321
uint keyspec = (uint) keyInfo.KeyNumber;
322
String provider = keyInfo.ProviderName;
323
uint cspflags = 0; //CryptoAPI Current User store; LM would be CRYPT_MACHINE_KEYSET = 0x00000020
324
String fname = keycontainer + ".p12" ;
325
//---- need to pass in rsa since underlying keycontainer is not persisted and might be deleted too quickly ---
326
byte[] pkcs12 = GetPkcs12(rsa, keycontainer, provider, keyspec , cspflags) ;
327
if ( (pkcs12 !=null) && verbose)
328
showBytes("\npkcs #12", pkcs12);
330
PutFileBytes(fname, pkcs12, pkcs12.Length) ;
331
Console.WriteLine("\nWrote pkc #12 file '{0}'\n", fname) ;
334
Console.WriteLine("\nProblem getting pkcs#12") ;
340
//-------- Get the binary PKCS #8 PRIVATE key --------
341
public static byte[] DecodePkcs8PrivateKey(String instr)
343
const String pemp8header = "-----BEGIN PRIVATE KEY-----" ;
344
const String pemp8footer = "-----END PRIVATE KEY-----" ;
345
String pemstr = instr.Trim() ;
347
if(!pemstr.StartsWith(pemp8header) || !pemstr.EndsWith(pemp8footer))
349
StringBuilder sb = new StringBuilder(pemstr) ;
350
sb.Replace(pemp8header, "") ; //remove headers/footers, if present
351
sb.Replace(pemp8footer, "") ;
353
String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
356
binkey = Convert.FromBase64String(pubstr) ;
358
catch(System.FormatException) { //if can't b64 decode, data is not valid
365
//------- Parses binary asn.1 PKCS #8 PrivateKeyInfo; returns RSACryptoServiceProvider ---
366
public static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
368
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
369
// this byte[] includes the sequence byte and terminal encoded null
370
byte[] SeqOID = {0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00} ;
371
byte[] seq = new byte[15];
372
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
373
MemoryStream mem = new MemoryStream(pkcs8) ;
374
int lenstream = (int) mem.Length;
375
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
381
twobytes = binr.ReadUInt16();
382
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
383
binr.ReadByte(); //advance 1 byte
384
else if(twobytes == 0x8230)
385
binr.ReadInt16(); //advance 2 bytes
390
bt = binr.ReadByte();
394
twobytes = binr.ReadUInt16();
396
if(twobytes != 0x0001)
399
seq = binr.ReadBytes(15); //read the Sequence OID
400
if(!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
403
bt = binr.ReadByte();
404
if(bt != 0x04) //expect an Octet string
407
bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count
413
//------ at this stage, the remaining sequence should be the RSA private key
415
byte[] rsaprivkey = binr.ReadBytes((int)(lenstream -mem.Position)) ;
416
RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
424
finally { binr.Close(); }
435
//-------- Get the binary PKCS #8 Encrypted PRIVATE key --------
436
public static byte[] DecodePkcs8EncPrivateKey(String instr)
438
const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----" ;
439
const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----" ;
440
String pemstr = instr.Trim() ;
442
if(!pemstr.StartsWith(pemp8encheader) || !pemstr.EndsWith(pemp8encfooter))
444
StringBuilder sb = new StringBuilder(pemstr) ;
445
sb.Replace(pemp8encheader, "") ; //remove headers/footers, if present
446
sb.Replace(pemp8encfooter, "") ;
448
String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
451
binkey = Convert.FromBase64String(pubstr) ;
453
catch(System.FormatException) { //if can't b64 decode, data is not valid
464
//------- Parses binary asn.1 EncryptedPrivateKeyInfo; returns RSACryptoServiceProvider ---
465
public static RSACryptoServiceProvider DecodeEncryptedPrivateKeyInfo(byte[] encpkcs8)
467
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
468
// this byte[] includes the sequence byte and terminal encoded null
469
byte[] OIDpkcs5PBES2 = {0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D } ;
470
byte[] OIDpkcs5PBKDF2 = {0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C } ;
471
byte[] OIDdesEDE3CBC = {0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07} ;
472
byte[] seqdes = new byte[10] ;
473
byte[] seq = new byte[11];
476
byte[] encryptedpkcs8;
479
int saltsize, ivsize, encblobsize;
482
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
483
MemoryStream mem = new MemoryStream(encpkcs8) ;
484
int lenstream = (int) mem.Length;
485
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
491
twobytes = binr.ReadUInt16();
492
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
493
binr.ReadByte(); //advance 1 byte
494
else if(twobytes == 0x8230)
495
binr.ReadInt16(); //advance 2 bytes
499
twobytes = binr.ReadUInt16(); //inner sequence
500
if(twobytes == 0x8130)
502
else if(twobytes == 0x8230)
506
seq = binr.ReadBytes(11); //read the Sequence OID
507
if(!CompareBytearrays(seq, OIDpkcs5PBES2)) //is it a OIDpkcs5PBES2 ?
510
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
511
if(twobytes == 0x8130)
513
else if(twobytes == 0x8230)
516
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
517
if(twobytes == 0x8130)
519
else if(twobytes == 0x8230)
522
seq = binr.ReadBytes(11); //read the Sequence OID
523
if(!CompareBytearrays(seq, OIDpkcs5PBKDF2)) //is it a OIDpkcs5PBKDF2 ?
526
twobytes = binr.ReadUInt16();
527
if(twobytes == 0x8130)
529
else if(twobytes == 0x8230)
532
bt = binr.ReadByte();
533
if(bt != 0x04) //expect octet string for salt
535
saltsize = binr.ReadByte();
536
salt = binr.ReadBytes(saltsize);
539
showBytes("Salt for pbkd", salt);
541
if (bt != 0x02) //expect an integer for PBKF2 interation count
544
int itbytes = binr.ReadByte(); //PBKD2 iterations should fit in 2 bytes.
546
iterations = binr.ReadByte();
547
else if(itbytes == 2)
548
iterations = 256*binr.ReadByte() + binr.ReadByte();
552
Console.WriteLine("PBKD2 iterations {0}", iterations);
554
twobytes = binr.ReadUInt16();
555
if(twobytes == 0x8130)
557
else if(twobytes == 0x8230)
561
seqdes = binr.ReadBytes(10); //read the Sequence OID
562
if(!CompareBytearrays(seqdes, OIDdesEDE3CBC)) //is it a OIDdes-EDE3-CBC ?
565
bt = binr.ReadByte();
566
if(bt != 0x04) //expect octet string for IV
568
ivsize = binr.ReadByte(); // IV byte size should fit in one byte (24 expected for 3DES)
569
IV= binr.ReadBytes(ivsize);
571
showBytes("IV for des-EDE3-CBC", IV);
574
if(bt != 0x04) // expect octet string for encrypted PKCS8 data
578
bt = binr.ReadByte();
581
encblobsize = binr.ReadByte(); // data size in next byte
583
encblobsize = 256*binr.ReadByte() + binr.ReadByte() ;
585
encblobsize = bt; // we already have the data size
588
encryptedpkcs8 = binr.ReadBytes(encblobsize) ;
590
// showBytes("Encrypted PKCS8 blob", encryptedpkcs8) ;
593
SecureString secpswd = GetSecPswd("Enter password for Encrypted PKCS #8 ==>") ;
594
pkcs8 = DecryptPBDK2(encryptedpkcs8, salt, IV, secpswd, iterations) ;
595
if(pkcs8 == null) // probably a bad pswd entered.
599
// showBytes("Decrypted PKCS #8", pkcs8) ;
600
//----- With a decrypted pkcs #8 PrivateKeyInfo blob, decode it to an RSA ---
601
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8) ;
609
finally { binr.Close(); }
618
// ------ Uses PBKD2 to derive a 3DES key and decrypts data --------
619
public static byte[] DecryptPBDK2(byte[] edata, byte[] salt, byte[]IV, SecureString secpswd, int iterations)
621
CryptoStream decrypt = null;
623
IntPtr unmanagedPswd = IntPtr.Zero;
624
byte[] psbytes = new byte[secpswd.Length] ;
625
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
626
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length) ;
627
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
631
Rfc2898DeriveBytes kd = new Rfc2898DeriveBytes(psbytes, salt, iterations);
632
TripleDES decAlg = TripleDES.Create();
633
decAlg.Key = kd.GetBytes(24);
635
MemoryStream memstr = new MemoryStream();
636
decrypt = new CryptoStream(memstr,decAlg.CreateDecryptor(), CryptoStreamMode.Write);
637
decrypt.Write(edata, 0, edata.Length);
639
decrypt.Close() ; // this is REQUIRED.
640
byte[] cleartext = memstr.ToArray();
645
Console.WriteLine("Problem decrypting: {0}", e.Message) ;
659
//-------- Get the binary RSA PUBLIC key --------
660
public static byte[] DecodeOpenSSLPublicKey(String instr)
662
const String pempubheader = "-----BEGIN PUBLIC KEY-----" ;
663
const String pempubfooter = "-----END PUBLIC KEY-----" ;
664
String pemstr = instr.Trim() ;
666
if(!pemstr.StartsWith(pempubheader) || !pemstr.EndsWith(pempubfooter))
668
StringBuilder sb = new StringBuilder(pemstr) ;
669
sb.Replace(pempubheader, "") ; //remove headers/footers, if present
670
sb.Replace(pempubfooter, "") ;
672
String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
675
binkey = Convert.FromBase64String(pubstr) ;
677
catch(System.FormatException) { //if can't b64 decode, data is not valid
685
//------- Parses binary asn.1 X509 SubjectPublicKeyInfo; returns RSACryptoServiceProvider ---
686
public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
688
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
689
byte[] SeqOID = {0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00} ;
690
byte[] seq = new byte[15];
691
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
692
MemoryStream mem = new MemoryStream(x509key) ;
693
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
699
twobytes = binr.ReadUInt16();
700
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
701
binr.ReadByte(); //advance 1 byte
702
else if(twobytes == 0x8230)
703
binr.ReadInt16(); //advance 2 bytes
707
seq = binr.ReadBytes(15); //read the Sequence OID
708
if(!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
711
twobytes = binr.ReadUInt16();
712
if(twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
713
binr.ReadByte(); //advance 1 byte
714
else if(twobytes == 0x8203)
715
binr.ReadInt16(); //advance 2 bytes
719
bt = binr.ReadByte();
720
if(bt != 0x00) //expect null byte next
723
twobytes = binr.ReadUInt16();
724
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
725
binr.ReadByte(); //advance 1 byte
726
else if(twobytes == 0x8230)
727
binr.ReadInt16(); //advance 2 bytes
731
twobytes = binr.ReadUInt16();
733
byte highbyte = 0x00;
735
if(twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
736
lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
737
else if(twobytes == 0x8202) {
738
highbyte = binr.ReadByte(); //advance 2 bytes
739
lowbyte = binr.ReadByte();
743
byte[] modint = {lowbyte, highbyte, 0x00, 0x00} ; //reverse byte order since asn.1 key uses big endian order
744
int modsize = BitConverter.ToInt32(modint, 0) ;
746
byte firstbyte = binr.ReadByte();
747
binr.BaseStream.Seek(-1, SeekOrigin.Current);
749
if(firstbyte == 0x00) { //if first byte (highest order) of modulus is zero, don't include it
750
binr.ReadByte(); //skip this null byte
751
modsize -=1 ; //reduce modulus buffer size by 1
754
byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
756
if(binr.ReadByte() != 0x02) //expect an Integer for the exponent data
758
int expbytes = (int) binr.ReadByte() ; // should only need one byte for actual exponent data (for all useful values)
759
byte[] exponent = binr.ReadBytes(expbytes);
762
showBytes("\nExponent", exponent);
763
showBytes("\nModulus", modulus) ;
765
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
766
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
767
RSAParameters RSAKeyInfo = new RSAParameters();
768
RSAKeyInfo.Modulus = modulus;
769
RSAKeyInfo.Exponent = exponent;
770
RSA.ImportParameters(RSAKeyInfo);
777
finally { binr.Close(); }
783
//------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider ---
784
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
786
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ ;
788
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
789
MemoryStream mem = new MemoryStream(privkey) ;
790
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
795
twobytes = binr.ReadUInt16();
796
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
797
binr.ReadByte(); //advance 1 byte
798
else if(twobytes == 0x8230)
799
binr.ReadInt16(); //advance 2 bytes
803
twobytes = binr.ReadUInt16();
804
if(twobytes != 0x0102) //version number
806
bt = binr.ReadByte();
811
//------ all private key components are Integer sequences ----
812
elems = GetIntegerSize(binr);
813
MODULUS = binr.ReadBytes(elems);
815
elems = GetIntegerSize(binr);
816
E = binr.ReadBytes(elems) ;
818
elems = GetIntegerSize(binr);
819
D = binr.ReadBytes(elems) ;
821
elems = GetIntegerSize(binr);
822
P = binr.ReadBytes(elems) ;
824
elems = GetIntegerSize(binr);
825
Q = binr.ReadBytes(elems) ;
827
elems = GetIntegerSize(binr);
828
DP = binr.ReadBytes(elems) ;
830
elems = GetIntegerSize(binr);
831
DQ = binr.ReadBytes(elems) ;
833
elems = GetIntegerSize(binr);
834
IQ = binr.ReadBytes(elems) ;
836
Console.WriteLine("showing components ..");
838
showBytes("\nModulus", MODULUS) ;
839
showBytes("\nExponent", E);
843
showBytes("\nDP", DP);
844
showBytes("\nDQ", DQ);
845
showBytes("\nIQ", IQ);
848
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
849
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
850
RSAParameters RSAparams = new RSAParameters();
851
RSAparams.Modulus =MODULUS;
852
RSAparams.Exponent = E;
858
RSAparams.InverseQ = IQ;
859
RSA.ImportParameters(RSAparams);
865
finally { binr.Close(); }
870
private static int GetIntegerSize(BinaryReader binr) {
873
byte highbyte = 0x00;
875
bt = binr.ReadByte();
876
if(bt != 0x02) //expect integer
878
bt = binr.ReadByte();
881
count = binr.ReadByte(); // data size in next byte
884
highbyte = binr.ReadByte(); // data size in next 2 bytes
885
lowbyte = binr.ReadByte();
886
byte[] modint = {lowbyte, highbyte, 0x00, 0x00} ;
887
count = BitConverter.ToInt32(modint, 0) ;
890
count = bt; // we already have the data size
895
while(binr.ReadByte() == 0x00) { //remove high order zeros in data
898
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
905
//----- Get the binary RSA PRIVATE key, decrypting if necessary ----
906
public static byte[] DecodeOpenSSLPrivateKey(String instr)
908
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----" ;
909
const String pemprivfooter = "-----END RSA PRIVATE KEY-----" ;
910
String pemstr = instr.Trim() ;
912
if(!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
915
StringBuilder sb = new StringBuilder(pemstr) ;
916
sb.Replace(pemprivheader, "") ; //remove headers/footers, if present
917
sb.Replace(pemprivfooter, "") ;
919
String pvkstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
921
try{ // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
922
binkey = Convert.FromBase64String(pvkstr) ;
925
catch(System.FormatException) { //if can't b64 decode, it must be an encrypted private key
926
//Console.WriteLine("Not an unencrypted OpenSSL PEM private key");
929
StringReader str = new StringReader(pvkstr);
931
//-------- read PEM encryption info. lines and extract salt -----
932
if(!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
934
String saltline = str.ReadLine();
935
if(!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,") )
937
String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim() ;
938
byte[] salt = new byte[saltstr.Length/2];
939
for (int i=0; i <salt.Length; i++)
940
salt[i] = Convert.ToByte(saltstr.Substring (i*2, 2), 16);
941
if(! (str.ReadLine() == ""))
944
//------ remaining b64 data is encrypted RSA key ----
945
String encryptedstr = str.ReadToEnd() ;
947
try{ //should have b64 encrypted RSA key now
948
binkey = Convert.FromBase64String(encryptedstr) ;
950
catch(System.FormatException) { // bad b64 data.
954
//------ Get the 3DES 24 byte key using PDK used by OpenSSL ----
956
SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>") ;
957
//Console.Write("\nEnter password to derive 3DES key: ");
958
//String pswd = Console.ReadLine();
959
byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
962
//showBytes("3DES key", deskey) ;
964
//------ Decrypt the encrypted 3des-encrypted RSA private key ------
965
byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
967
return rsakey; //we have a decrypted RSA private key
969
Console.WriteLine("Failed to decrypt RSA private key; probably wrong password.");
977
// ----- Decrypt the 3DES encrypted RSA private key ----------
979
public static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
981
MemoryStream memst = new MemoryStream();
982
TripleDES alg = TripleDES.Create();
986
CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
987
cs.Write(cipherData, 0, cipherData.Length);
990
catch(Exception exc){
991
Console.WriteLine(exc.Message);
993
byte[] decryptedData = memst.ToArray();
994
return decryptedData;
1000
//----- OpenSSL PBKD uses only one hash cycle (count); miter is number of iterations required to build sufficient bytes ---
1001
private static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter ) {
1002
IntPtr unmanagedPswd = IntPtr.Zero;
1003
int HASHLENGTH = 16; //MD5 bytes
1004
byte[] keymaterial = new byte[HASHLENGTH*miter] ; //to store contatenated Mi hashed results
1007
byte[] psbytes = new byte[secpswd.Length] ;
1008
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
1009
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length) ;
1010
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
1012
//UTF8Encoding utf8 = new UTF8Encoding();
1013
//byte[] psbytes = utf8.GetBytes(pswd);
1015
// --- contatenate salt and pswd bytes into fixed data array ---
1016
byte[] data00 = new byte[psbytes.Length + salt.Length] ;
1017
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
1018
Array.Copy(salt, 0, data00, psbytes.Length, salt.Length) ; //concatenate the salt bytes
1020
// ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ----
1021
MD5 md5 = new MD5CryptoServiceProvider();
1022
byte[] result = null;
1023
byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
1025
for(int j=0; j<miter; j++)
1027
// ---- Now hash consecutively for count times ------
1029
result = data00; //initialize
1031
Array.Copy(result, hashtarget, result.Length);
1032
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length) ;
1033
result = hashtarget;
1034
//Console.WriteLine("Updated new initial hash target:") ;
1035
//showBytes(result) ;
1038
for(int i=0; i<count; i++)
1039
result = md5.ComputeHash(result);
1040
Array.Copy(result, 0, keymaterial, j*HASHLENGTH, result.Length); //contatenate to keymaterial
1042
//showBytes("Final key material", keymaterial);
1043
byte[] deskey = new byte[24];
1044
Array.Copy(keymaterial, deskey, deskey.Length) ;
1046
Array.Clear(psbytes, 0, psbytes.Length);
1047
Array.Clear(data00, 0, data00.Length) ;
1048
Array.Clear(result, 0, result.Length) ;
1049
Array.Clear(hashtarget, 0, hashtarget.Length) ;
1050
Array.Clear(keymaterial, 0, keymaterial.Length) ;
1060
//------ Since we are using an RSA with nonpersisted keycontainer, must pass it in to ensure it isn't colledted -----
1061
private static byte[] GetPkcs12(RSA rsa, String keycontainer, String cspprovider, uint KEYSPEC, uint cspflags)
1063
byte[] pfxblob = null;
1064
IntPtr hCertCntxt = IntPtr.Zero;
1066
String DN = "CN=Opensslkey Unsigned Certificate";
1068
hCertCntxt = CreateUnsignedCertCntxt(keycontainer, cspprovider, KEYSPEC, cspflags, DN) ;
1069
if(hCertCntxt == IntPtr.Zero){
1070
Console.WriteLine("Couldn't create an unsigned-cert\n") ;
1074
X509Certificate cert = new X509Certificate(hCertCntxt) ; //create certificate object from cert context.
1075
X509Certificate2UI.DisplayCertificate(new X509Certificate2(cert)) ; // display it, showing linked private key
1076
SecureString pswd = GetSecPswd("Set PFX Password ==>") ;
1077
pfxblob = cert.Export(X509ContentType.Pkcs12, pswd);
1080
catch(Exception exc)
1082
Console.WriteLine( "BAD RESULT" + exc.Message);
1087
if(hCertCntxt != IntPtr.Zero)
1088
Win32.CertFreeCertificateContext(hCertCntxt) ;
1095
private static IntPtr CreateUnsignedCertCntxt(String keycontainer, String provider, uint KEYSPEC, uint cspflags, String DN) {
1096
const uint AT_KEYEXCHANGE = 0x00000001;
1097
const uint AT_SIGNATURE = 0x00000002;
1098
const uint CRYPT_MACHINE_KEYSET = 0x00000020;
1099
const uint PROV_RSA_FULL = 0x00000001;
1100
const String MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0";
1101
const String MS_STRONG_PROV = "Microsoft Strong Cryptographic Provider";
1102
const String MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider v1.0";
1103
const uint CERT_CREATE_SELFSIGN_NO_SIGN = 1 ;
1104
const uint X509_ASN_ENCODING = 0x00000001;
1105
const uint CERT_X500_NAME_STR = 3;
1106
IntPtr hCertCntxt = IntPtr.Zero;
1107
byte[] encodedName = null;
1110
if( provider != MS_DEF_PROV && provider != MS_STRONG_PROV && provider != MS_ENHANCED_PROV)
1112
if(keycontainer == "")
1114
if( KEYSPEC != AT_SIGNATURE && KEYSPEC != AT_KEYEXCHANGE)
1116
if(cspflags != 0 && cspflags != CRYPT_MACHINE_KEYSET) //only 0 (Current User) keyset is currently used.
1122
if(Win32.CertStrToName(X509_ASN_ENCODING, DN, CERT_X500_NAME_STR, IntPtr.Zero, null, ref cbName, IntPtr.Zero))
1124
encodedName = new byte[cbName] ;
1125
Win32.CertStrToName(X509_ASN_ENCODING, DN, CERT_X500_NAME_STR, IntPtr.Zero, encodedName, ref cbName, IntPtr.Zero);
1128
CERT_NAME_BLOB subjectblob = new CERT_NAME_BLOB();
1129
subjectblob.pbData = Marshal.AllocHGlobal(encodedName.Length);
1130
Marshal.Copy(encodedName, 0, subjectblob.pbData, encodedName.Length);
1131
subjectblob.cbData = encodedName.Length;
1133
CRYPT_KEY_PROV_INFO pInfo = new CRYPT_KEY_PROV_INFO();
1134
pInfo.pwszContainerName = keycontainer;
1135
pInfo.pwszProvName = provider;
1136
pInfo.dwProvType = PROV_RSA_FULL;
1137
pInfo.dwFlags = cspflags;
1138
pInfo.cProvParam = 0;
1139
pInfo.rgProvParam = IntPtr.Zero;
1140
pInfo.dwKeySpec = KEYSPEC;
1142
hCertCntxt = Win32.CertCreateSelfSignCertificate(IntPtr.Zero, ref subjectblob, CERT_CREATE_SELFSIGN_NO_SIGN, ref pInfo, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
1143
if(hCertCntxt == IntPtr.Zero)
1144
showWin32Error(Marshal.GetLastWin32Error());
1145
Marshal.FreeHGlobal(subjectblob.pbData);
1152
private static SecureString GetSecPswd(String prompt)
1154
SecureString password = new SecureString();
1156
Console.ForegroundColor = ConsoleColor.Gray;
1157
Console.Write(prompt);
1158
Console.ForegroundColor = ConsoleColor.Magenta;
1162
ConsoleKeyInfo cki = Console.ReadKey(true);
1163
if (cki.Key == ConsoleKey.Enter)
1165
Console.ForegroundColor = ConsoleColor.Gray;
1166
Console.WriteLine();
1169
else if (cki.Key == ConsoleKey.Backspace)
1171
// remove the last asterisk from the screen...
1172
if (password.Length > 0)
1174
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
1176
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
1177
password.RemoveAt(password.Length - 1);
1180
else if (cki.Key == ConsoleKey.Escape)
1182
Console.ForegroundColor = ConsoleColor.Gray;
1183
Console.WriteLine();
1186
else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar))
1188
if (password.Length < 20)
1190
password.AppendChar(cki.KeyChar);
1209
private static bool CompareBytearrays(byte [] a, byte[] b)
1211
if(a.Length != b.Length)
1214
foreach(byte c in a)
1225
private static void showRSAProps(RSACryptoServiceProvider rsa) {
1226
Console.WriteLine("RSA CSP key information:");
1227
CspKeyContainerInfo keyInfo = rsa.CspKeyContainerInfo;
1228
Console.WriteLine("Accessible property: " + keyInfo.Accessible);
1229
Console.WriteLine("Exportable property: " + keyInfo.Exportable);
1230
Console.WriteLine("HardwareDevice property: " + keyInfo.HardwareDevice);
1231
Console.WriteLine("KeyContainerName property: " + keyInfo.KeyContainerName);
1232
Console.WriteLine("KeyNumber property: " + keyInfo.KeyNumber.ToString());
1233
Console.WriteLine("MachineKeyStore property: " + keyInfo.MachineKeyStore);
1234
Console.WriteLine("Protected property: " + keyInfo.Protected);
1235
Console.WriteLine("ProviderName property: " + keyInfo.ProviderName);
1236
Console.WriteLine("ProviderType property: " + keyInfo.ProviderType);
1237
Console.WriteLine("RandomlyGenerated property: " + keyInfo.RandomlyGenerated);
1238
Console.WriteLine("Removable property: " + keyInfo.Removable);
1239
Console.WriteLine("UniqueKeyContainerName property: " + keyInfo.UniqueKeyContainerName);
1244
private static void showBytes(String info, byte[] data){
1245
Console.WriteLine("{0} [{1} bytes]", info, data.Length);
1246
for(int i=1; i<=data.Length; i++){
1247
Console.Write("{0:X2} ", data[i-1]) ;
1249
Console.WriteLine();
1251
Console.WriteLine("\n\n");
1255
private static byte[] GetFileBytes(String filename){
1256
if(!File.Exists(filename))
1258
Stream stream=new FileStream(filename,FileMode.Open);
1259
int datalen = (int)stream.Length;
1260
byte[] filebytes =new byte[datalen];
1261
stream.Seek(0,SeekOrigin.Begin);
1262
stream.Read(filebytes,0,datalen);
1268
private static void PutFileBytes(String outfile, byte[] data, int bytes) {
1269
FileStream fs = null;
1270
if(bytes > data.Length) {
1271
Console.WriteLine("Too many bytes");
1275
fs = new FileStream(outfile, FileMode.Create);
1276
fs.Write(data, 0, bytes);
1278
catch(Exception e) {
1279
Console.WriteLine(e.Message) ;
1288
private static void showWin32Error(int errorcode)
1290
Win32Exception myEx=new Win32Exception(errorcode);
1291
Console.ForegroundColor = ConsoleColor.Red;
1292
Console.WriteLine("Error code:\t 0x{0:X}", myEx.ErrorCode);
1293
Console.WriteLine("Error message:\t {0}\n", myEx.Message);
1294
Console.ForegroundColor = ConsoleColor.Gray;