Skip to content

Instantly share code, notes, and snippets.

@stormwild
Created December 10, 2013 08:19
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save stormwild/7887264 to your computer and use it in GitHub Desktop.
Save stormwild/7887264 to your computer and use it in GitHub Desktop.
//**********************************************************************************
//
//OpenSSLKey
// .NET 2.0 OpenSSL Public & Private Key Parser
//
// Copyright (C) 2008 JavaScience Consulting
//
//***********************************************************************************
//
// opensslkey.cs
//
// Reads and parses:
// (1) OpenSSL PEM or DER public keys
// (2) OpenSSL PEM or DER traditional SSLeay private keys (encrypted and unencrypted)
// (3) PKCS #8 PEM or DER encoded private keys (encrypted and unencrypted)
// Keys in PEM format must have headers/footers .
// Encrypted Private Key in SSLEay format not supported in DER
// Removes header/footer lines.
// For traditional SSLEAY PEM private keys, checks for encrypted format and
// uses PBE to extract 3DES key.
// For SSLEAY format, only supports encryption format: DES-EDE3-CBC
// For PKCS #8, only supports PKCS#5 v2.0 3des.
// Parses private and public key components and returns .NET RSA object.
// Creates dummy unsigned certificate linked to private keypair and
// optionally exports to pkcs #12
//
// See also:
// http://www.openssl.org/docs/crypto/pem.html#PEM_ENCRYPTION_FORMAT
//**************************************************************************************
using System;
using System.IO;
using System.Text;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.Security;
using System.Diagnostics;
using System.ComponentModel;
namespace JavaScience {
public class Win32 {
[DllImport("crypt32.dll", SetLastError=true)]
public static extern IntPtr CertCreateSelfSignCertificate(
IntPtr hProv,
ref CERT_NAME_BLOB pSubjectIssuerBlob,
uint dwFlagsm,
ref CRYPT_KEY_PROV_INFO pKeyProvInfo,
IntPtr pSignatureAlgorithm,
IntPtr pStartTime,
IntPtr pEndTime,
IntPtr other) ;
[DllImport("crypt32.dll", SetLastError=true)]
public static extern bool CertStrToName(
uint dwCertEncodingType,
String pszX500,
uint dwStrType,
IntPtr pvReserved,
[In, Out] byte[] pbEncoded,
ref uint pcbEncoded,
IntPtr other);
[DllImport("crypt32.dll", SetLastError=true)]
public static extern bool CertFreeCertificateContext(
IntPtr hCertStore) ;
}
[StructLayout(LayoutKind.Sequential)]
public struct CRYPT_KEY_PROV_INFO
{
[MarshalAs(UnmanagedType.LPWStr)] public String pwszContainerName;
[MarshalAs(UnmanagedType.LPWStr)] public String pwszProvName;
public uint dwProvType;
public uint dwFlags;
public uint cProvParam;
public IntPtr rgProvParam;
public uint dwKeySpec;
}
[StructLayout(LayoutKind.Sequential)]
public struct CERT_NAME_BLOB
{
public int cbData;
public IntPtr pbData;
}
public class opensslkey {
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----" ;
const String pemprivfooter = "-----END RSA PRIVATE KEY-----" ;
const String pempubheader = "-----BEGIN PUBLIC KEY-----" ;
const String pempubfooter = "-----END PUBLIC KEY-----" ;
const String pemp8header = "-----BEGIN PRIVATE KEY-----" ;
const String pemp8footer = "-----END PRIVATE KEY-----" ;
const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----" ;
const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----" ;
// static byte[] pempublickey;
// static byte[] pemprivatekey;
// static byte[] pkcs8privatekey;
// static byte[] pkcs8encprivatekey;
static bool verbose = false;
public static void Main(String[] args) {
if(args.Length == 1)
if(args[0].ToUpper() == "V")
verbose = true;
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write("\nRSA public, private or PKCS #8 key file to decode: ");
String filename = Console.ReadLine().Trim();
if (filename == "") //exit while(true) loop
return;
if (!File.Exists(filename)) {
Console.WriteLine("File \"{0}\" does not exist!\n", filename);
return;
}
StreamReader sr = File.OpenText(filename);
String pemstr = sr.ReadToEnd().Trim();
sr.Close();
if(pemstr.StartsWith("-----BEGIN"))
DecodePEMKey(pemstr);
else
DecodeDERKey(filename);
}
// ------- Decode PEM pubic, private or pkcs8 key ----------------
public static void DecodePEMKey(String pemstr)
{
byte[] pempublickey;
byte[] pemprivatekey;
byte[] pkcs8privatekey;
byte[] pkcs8encprivatekey;
if(pemstr.StartsWith(pempubheader) && pemstr.EndsWith(pempubfooter))
{
Console.WriteLine("Trying to decode and parse a PEM public key ..");
pempublickey = DecodeOpenSSLPublicKey(pemstr);
if(pempublickey != null)
{
if(verbose)
showBytes("\nRSA public key", pempublickey) ;
//PutFileBytes("rsapubkey.pem", pempublickey, pempublickey.Length) ;
RSACryptoServiceProvider rsa = DecodeX509PublicKey(pempublickey);
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlpublickey =rsa.ToXmlString(false) ;
Console.WriteLine("\nXML RSA public key: {0} bits\n{1}\n", rsa.KeySize, xmlpublickey) ;
}
}
else if(pemstr.StartsWith(pemprivheader) && pemstr.EndsWith(pemprivfooter))
{
Console.WriteLine("Trying to decrypt and parse a PEM private key ..");
pemprivatekey = DecodeOpenSSLPrivateKey(pemstr);
if(pemprivatekey != null)
{
if(verbose)
showBytes("\nRSA private key", pemprivatekey) ;
//PutFileBytes("rsaprivkey.pem", pemprivatekey, pemprivatekey.Length) ;
RSACryptoServiceProvider rsa = DecodeRSAPrivateKey(pemprivatekey);
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlprivatekey =rsa.ToXmlString(true) ;
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
ProcessRSA(rsa);
}
}
else if(pemstr.StartsWith(pemp8header) && pemstr.EndsWith(pemp8footer))
{
Console.WriteLine("Trying to decode and parse as PEM PKCS #8 PrivateKeyInfo ..");
pkcs8privatekey = DecodePkcs8PrivateKey(pemstr);
if(pkcs8privatekey != null)
{
if(verbose)
showBytes("\nPKCS #8 PrivateKeyInfo", pkcs8privatekey) ;
//PutFileBytes("PrivateKeyInfo", pkcs8privatekey, pkcs8privatekey.Length) ;
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey);
if(rsa !=null)
{
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlprivatekey =rsa.ToXmlString(true) ;
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
ProcessRSA(rsa) ;
}
else
Console.WriteLine("\nFailed to create an RSACryptoServiceProvider");
}
}
else if(pemstr.StartsWith(pemp8encheader) && pemstr.EndsWith(pemp8encfooter))
{
Console.WriteLine("Trying to decode and parse as PEM PKCS #8 EncryptedPrivateKeyInfo ..");
pkcs8encprivatekey = DecodePkcs8EncPrivateKey(pemstr);
if(pkcs8encprivatekey != null)
{
if(verbose)
showBytes("\nPKCS #8 EncryptedPrivateKeyInfo", pkcs8encprivatekey) ;
//PutFileBytes("EncryptedPrivateKeyInfo", pkcs8encprivatekey, pkcs8encprivatekey.Length) ;
RSACryptoServiceProvider rsa = DecodeEncryptedPrivateKeyInfo(pkcs8encprivatekey);
if(rsa !=null)
{
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlprivatekey =rsa.ToXmlString(true) ;
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
ProcessRSA(rsa) ;
}
else
Console.WriteLine("\nFailed to create an RSACryptoServiceProvider");
}
}
else
{
Console.WriteLine("Not a PEM public, private key or a PKCS #8");
return;
}
}
// ------- Decode PEM pubic, private or pkcs8 key ----------------
public static void DecodeDERKey(String filename)
{
RSACryptoServiceProvider rsa = null ;
byte[] keyblob = GetFileBytes(filename);
if(keyblob == null)
return;
rsa = DecodeX509PublicKey(keyblob);
if(rsa !=null)
{
Console.WriteLine("\nA valid SubjectPublicKeyInfo\n") ;
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlpublickey =rsa.ToXmlString(false) ;
Console.WriteLine("\nXML RSA public key: {0} bits\n{1}\n", rsa.KeySize, xmlpublickey) ;
return;
}
rsa = DecodeRSAPrivateKey(keyblob);
if(rsa != null)
{
Console.WriteLine("\nA valid RSAPrivateKey\n") ;
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlprivatekey =rsa.ToXmlString(true) ;
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
ProcessRSA(rsa) ;
return;
}
rsa = DecodePrivateKeyInfo(keyblob); //PKCS #8 unencrypted
if(rsa !=null)
{
Console.WriteLine("\nA valid PKCS #8 PrivateKeyInfo\n") ;
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlprivatekey =rsa.ToXmlString(true) ;
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
ProcessRSA(rsa);
return;
}
rsa = DecodeEncryptedPrivateKeyInfo(keyblob); //PKCS #8 encrypted
if(rsa !=null) {
Console.WriteLine("\nA valid PKCS #8 EncryptedPrivateKeyInfo\n") ;
Console.WriteLine("\nCreated an RSACryptoServiceProvider instance\n") ;
String xmlprivatekey =rsa.ToXmlString(true) ;
Console.WriteLine("\nXML RSA private key: {0} bits\n{1}\n", rsa.KeySize, xmlprivatekey) ;
ProcessRSA(rsa);
return;
}
Console.WriteLine("Not a binary DER public, private or PKCS #8 key");
return;
}
public static void ProcessRSA(RSACryptoServiceProvider rsa)
{
if(verbose)
showRSAProps(rsa);
Console.Write("\n\nExport RSA private key to PKCS #12 file? (Y or N) ");
String resp = Console.ReadLine().ToUpper() ;
if(resp == "Y" || resp == "YES")
RSAtoPKCS12(rsa) ;
}
//-------- Generate pkcs #12 from an RSACryptoServiceProvider ---------
public static void RSAtoPKCS12(RSACryptoServiceProvider rsa)
{
CspKeyContainerInfo keyInfo = rsa.CspKeyContainerInfo;
String keycontainer = keyInfo.KeyContainerName;
uint keyspec = (uint) keyInfo.KeyNumber;
String provider = keyInfo.ProviderName;
uint cspflags = 0; //CryptoAPI Current User store; LM would be CRYPT_MACHINE_KEYSET = 0x00000020
String fname = keycontainer + ".p12" ;
//---- need to pass in rsa since underlying keycontainer is not persisted and might be deleted too quickly ---
byte[] pkcs12 = GetPkcs12(rsa, keycontainer, provider, keyspec , cspflags) ;
if ( (pkcs12 !=null) && verbose)
showBytes("\npkcs #12", pkcs12);
if(pkcs12 !=null){
PutFileBytes(fname, pkcs12, pkcs12.Length) ;
Console.WriteLine("\nWrote pkc #12 file '{0}'\n", fname) ;
}
else
Console.WriteLine("\nProblem getting pkcs#12") ;
}
//-------- Get the binary PKCS #8 PRIVATE key --------
public static byte[] DecodePkcs8PrivateKey(String instr)
{
const String pemp8header = "-----BEGIN PRIVATE KEY-----" ;
const String pemp8footer = "-----END PRIVATE KEY-----" ;
String pemstr = instr.Trim() ;
byte[] binkey;
if(!pemstr.StartsWith(pemp8header) || !pemstr.EndsWith(pemp8footer))
return null;
StringBuilder sb = new StringBuilder(pemstr) ;
sb.Replace(pemp8header, "") ; //remove headers/footers, if present
sb.Replace(pemp8footer, "") ;
String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try{
binkey = Convert.FromBase64String(pubstr) ;
}
catch(System.FormatException) { //if can't b64 decode, data is not valid
return null;
}
return binkey;
}
//------- Parses binary asn.1 PKCS #8 PrivateKeyInfo; returns RSACryptoServiceProvider ---
public static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
// this byte[] includes the sequence byte and terminal encoded null
byte[] SeqOID = {0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00} ;
byte[] seq = new byte[15];
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(pkcs8) ;
int lenstream = (int) mem.Length;
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try{
twobytes = binr.ReadUInt16();
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if(twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
bt = binr.ReadByte();
if(bt != 0x02)
return null;
twobytes = binr.ReadUInt16();
if(twobytes != 0x0001)
return null;
seq = binr.ReadBytes(15); //read the Sequence OID
if(!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
return null;
bt = binr.ReadByte();
if(bt != 0x04) //expect an Octet string
return null;
bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count
if(bt == 0x81)
binr.ReadByte();
else
if(bt == 0x82)
binr.ReadUInt16();
//------ at this stage, the remaining sequence should be the RSA private key
byte[] rsaprivkey = binr.ReadBytes((int)(lenstream -mem.Position)) ;
RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey);
return rsacsp;
}
catch(Exception){
return null;
}
finally { binr.Close(); }
}
//-------- Get the binary PKCS #8 Encrypted PRIVATE key --------
public static byte[] DecodePkcs8EncPrivateKey(String instr)
{
const String pemp8encheader = "-----BEGIN ENCRYPTED PRIVATE KEY-----" ;
const String pemp8encfooter = "-----END ENCRYPTED PRIVATE KEY-----" ;
String pemstr = instr.Trim() ;
byte[] binkey;
if(!pemstr.StartsWith(pemp8encheader) || !pemstr.EndsWith(pemp8encfooter))
return null;
StringBuilder sb = new StringBuilder(pemstr) ;
sb.Replace(pemp8encheader, "") ; //remove headers/footers, if present
sb.Replace(pemp8encfooter, "") ;
String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try{
binkey = Convert.FromBase64String(pubstr) ;
}
catch(System.FormatException) { //if can't b64 decode, data is not valid
return null;
}
return binkey;
}
//------- Parses binary asn.1 EncryptedPrivateKeyInfo; returns RSACryptoServiceProvider ---
public static RSACryptoServiceProvider DecodeEncryptedPrivateKeyInfo(byte[] encpkcs8)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
// this byte[] includes the sequence byte and terminal encoded null
byte[] OIDpkcs5PBES2 = {0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D } ;
byte[] OIDpkcs5PBKDF2 = {0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C } ;
byte[] OIDdesEDE3CBC = {0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x07} ;
byte[] seqdes = new byte[10] ;
byte[] seq = new byte[11];
byte[] salt ;
byte[] IV;
byte[] encryptedpkcs8;
byte[] pkcs8;
int saltsize, ivsize, encblobsize;
int iterations;
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(encpkcs8) ;
int lenstream = (int) mem.Length;
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try{
twobytes = binr.ReadUInt16();
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if(twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16(); //inner sequence
if(twobytes == 0x8130)
binr.ReadByte();
else if(twobytes == 0x8230)
binr.ReadInt16();
seq = binr.ReadBytes(11); //read the Sequence OID
if(!CompareBytearrays(seq, OIDpkcs5PBES2)) //is it a OIDpkcs5PBES2 ?
return null;
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
if(twobytes == 0x8130)
binr.ReadByte();
else if(twobytes == 0x8230)
binr.ReadInt16();
twobytes = binr.ReadUInt16(); //inner sequence for pswd salt
if(twobytes == 0x8130)
binr.ReadByte();
else if(twobytes == 0x8230)
binr.ReadInt16();
seq = binr.ReadBytes(11); //read the Sequence OID
if(!CompareBytearrays(seq, OIDpkcs5PBKDF2)) //is it a OIDpkcs5PBKDF2 ?
return null;
twobytes = binr.ReadUInt16();
if(twobytes == 0x8130)
binr.ReadByte();
else if(twobytes == 0x8230)
binr.ReadInt16();
bt = binr.ReadByte();
if(bt != 0x04) //expect octet string for salt
return null;
saltsize = binr.ReadByte();
salt = binr.ReadBytes(saltsize);
if(verbose)
showBytes("Salt for pbkd", salt);
bt=binr.ReadByte();
if (bt != 0x02) //expect an integer for PBKF2 interation count
return null;
int itbytes = binr.ReadByte(); //PBKD2 iterations should fit in 2 bytes.
if(itbytes ==1)
iterations = binr.ReadByte();
else if(itbytes == 2)
iterations = 256*binr.ReadByte() + binr.ReadByte();
else
return null;
if(verbose)
Console.WriteLine("PBKD2 iterations {0}", iterations);
twobytes = binr.ReadUInt16();
if(twobytes == 0x8130)
binr.ReadByte();
else if(twobytes == 0x8230)
binr.ReadInt16();
seqdes = binr.ReadBytes(10); //read the Sequence OID
if(!CompareBytearrays(seqdes, OIDdesEDE3CBC)) //is it a OIDdes-EDE3-CBC ?
return null;
bt = binr.ReadByte();
if(bt != 0x04) //expect octet string for IV
return null;
ivsize = binr.ReadByte(); // IV byte size should fit in one byte (24 expected for 3DES)
IV= binr.ReadBytes(ivsize);
if(verbose)
showBytes("IV for des-EDE3-CBC", IV);
bt=binr.ReadByte();
if(bt != 0x04) // expect octet string for encrypted PKCS8 data
return null;
bt = binr.ReadByte();
if(bt == 0x81)
encblobsize = binr.ReadByte(); // data size in next byte
else if(bt == 0x82)
encblobsize = 256*binr.ReadByte() + binr.ReadByte() ;
else
encblobsize = bt; // we already have the data size
encryptedpkcs8 = binr.ReadBytes(encblobsize) ;
//if(verbose)
// showBytes("Encrypted PKCS8 blob", encryptedpkcs8) ;
SecureString secpswd = GetSecPswd("Enter password for Encrypted PKCS #8 ==>") ;
pkcs8 = DecryptPBDK2(encryptedpkcs8, salt, IV, secpswd, iterations) ;
if(pkcs8 == null) // probably a bad pswd entered.
return null;
//if(verbose)
// showBytes("Decrypted PKCS #8", pkcs8) ;
//----- With a decrypted pkcs #8 PrivateKeyInfo blob, decode it to an RSA ---
RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8) ;
return rsa;
}
catch(Exception){
return null;
}
finally { binr.Close(); }
}
// ------ Uses PBKD2 to derive a 3DES key and decrypts data --------
public static byte[] DecryptPBDK2(byte[] edata, byte[] salt, byte[]IV, SecureString secpswd, int iterations)
{
CryptoStream decrypt = null;
IntPtr unmanagedPswd = IntPtr.Zero;
byte[] psbytes = new byte[secpswd.Length] ;
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length) ;
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
try
{
Rfc2898DeriveBytes kd = new Rfc2898DeriveBytes(psbytes, salt, iterations);
TripleDES decAlg = TripleDES.Create();
decAlg.Key = kd.GetBytes(24);
decAlg.IV = IV;
MemoryStream memstr = new MemoryStream();
decrypt = new CryptoStream(memstr,decAlg.CreateDecryptor(), CryptoStreamMode.Write);
decrypt.Write(edata, 0, edata.Length);
decrypt.Flush();
decrypt.Close() ; // this is REQUIRED.
byte[] cleartext = memstr.ToArray();
return cleartext;
}
catch (Exception e)
{
Console.WriteLine("Problem decrypting: {0}", e.Message) ;
return null;
}
}
//-------- Get the binary RSA PUBLIC key --------
public static byte[] DecodeOpenSSLPublicKey(String instr)
{
const String pempubheader = "-----BEGIN PUBLIC KEY-----" ;
const String pempubfooter = "-----END PUBLIC KEY-----" ;
String pemstr = instr.Trim() ;
byte[] binkey;
if(!pemstr.StartsWith(pempubheader) || !pemstr.EndsWith(pempubfooter))
return null;
StringBuilder sb = new StringBuilder(pemstr) ;
sb.Replace(pempubheader, "") ; //remove headers/footers, if present
sb.Replace(pempubfooter, "") ;
String pubstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try{
binkey = Convert.FromBase64String(pubstr) ;
}
catch(System.FormatException) { //if can't b64 decode, data is not valid
return null;
}
return binkey;
}
//------- Parses binary asn.1 X509 SubjectPublicKeyInfo; returns RSACryptoServiceProvider ---
public static RSACryptoServiceProvider DecodeX509PublicKey(byte[] x509key)
{
// encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
byte[] SeqOID = {0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00} ;
byte[] seq = new byte[15];
// --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
MemoryStream mem = new MemoryStream(x509key) ;
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
try{
twobytes = binr.ReadUInt16();
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if(twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
seq = binr.ReadBytes(15); //read the Sequence OID
if(!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
return null;
twobytes = binr.ReadUInt16();
if(twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
binr.ReadByte(); //advance 1 byte
else if(twobytes == 0x8203)
binr.ReadInt16(); //advance 2 bytes
else
return null;
bt = binr.ReadByte();
if(bt != 0x00) //expect null byte next
return null;
twobytes = binr.ReadUInt16();
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if(twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
byte lowbyte = 0x00;
byte highbyte = 0x00;
if(twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
else if(twobytes == 0x8202) {
highbyte = binr.ReadByte(); //advance 2 bytes
lowbyte = binr.ReadByte();
}
else
return null;
byte[] modint = {lowbyte, highbyte, 0x00, 0x00} ; //reverse byte order since asn.1 key uses big endian order
int modsize = BitConverter.ToInt32(modint, 0) ;
byte firstbyte = binr.ReadByte();
binr.BaseStream.Seek(-1, SeekOrigin.Current);
if(firstbyte == 0x00) { //if first byte (highest order) of modulus is zero, don't include it
binr.ReadByte(); //skip this null byte
modsize -=1 ; //reduce modulus buffer size by 1
}
byte[] modulus = binr.ReadBytes(modsize); //read the modulus bytes
if(binr.ReadByte() != 0x02) //expect an Integer for the exponent data
return null;
int expbytes = (int) binr.ReadByte() ; // should only need one byte for actual exponent data (for all useful values)
byte[] exponent = binr.ReadBytes(expbytes);
showBytes("\nExponent", exponent);
showBytes("\nModulus", modulus) ;
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAKeyInfo = new RSAParameters();
RSAKeyInfo.Modulus = modulus;
RSAKeyInfo.Exponent = exponent;
RSA.ImportParameters(RSAKeyInfo);
return RSA;
}
catch(Exception){
return null;
}
finally { binr.Close(); }
}
//------- Parses binary ans.1 RSA private key; returns RSACryptoServiceProvider ---
public static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey)
{
byte[] MODULUS, E, D, P, Q, DP, DQ, IQ ;
// --------- Set up stream to decode the asn.1 encoded RSA private key ------
MemoryStream mem = new MemoryStream(privkey) ;
BinaryReader binr = new BinaryReader(mem) ; //wrap Memory Stream with BinaryReader for easy reading
byte bt = 0;
ushort twobytes = 0;
int elems = 0;
try{
twobytes = binr.ReadUInt16();
if(twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
binr.ReadByte(); //advance 1 byte
else if(twobytes == 0x8230)
binr.ReadInt16(); //advance 2 bytes
else
return null;
twobytes = binr.ReadUInt16();
if(twobytes != 0x0102) //version number
return null;
bt = binr.ReadByte();
if(bt !=0x00)
return null;
//------ all private key components are Integer sequences ----
elems = GetIntegerSize(binr);
MODULUS = binr.ReadBytes(elems);
elems = GetIntegerSize(binr);
E = binr.ReadBytes(elems) ;
elems = GetIntegerSize(binr);
D = binr.ReadBytes(elems) ;
elems = GetIntegerSize(binr);
P = binr.ReadBytes(elems) ;
elems = GetIntegerSize(binr);
Q = binr.ReadBytes(elems) ;
elems = GetIntegerSize(binr);
DP = binr.ReadBytes(elems) ;
elems = GetIntegerSize(binr);
DQ = binr.ReadBytes(elems) ;
elems = GetIntegerSize(binr);
IQ = binr.ReadBytes(elems) ;
Console.WriteLine("showing components ..");
if(verbose) {
showBytes("\nModulus", MODULUS) ;
showBytes("\nExponent", E);
showBytes("\nD", D);
showBytes("\nP", P);
showBytes("\nQ", Q);
showBytes("\nDP", DP);
showBytes("\nDQ", DQ);
showBytes("\nIQ", IQ);
}
// ------- create RSACryptoServiceProvider instance and initialize with public key -----
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
RSAParameters RSAparams = new RSAParameters();
RSAparams.Modulus =MODULUS;
RSAparams.Exponent = E;
RSAparams.D = D;
RSAparams.P = P;
RSAparams.Q = Q;
RSAparams.DP = DP;
RSAparams.DQ = DQ;
RSAparams.InverseQ = IQ;
RSA.ImportParameters(RSAparams);
return RSA;
}
catch(Exception){
return null;
}
finally { binr.Close(); }
}
private static int GetIntegerSize(BinaryReader binr) {
byte bt = 0;
byte lowbyte = 0x00;
byte highbyte = 0x00;
int count = 0;
bt = binr.ReadByte();
if(bt != 0x02) //expect integer
return 0;
bt = binr.ReadByte();
if(bt == 0x81)
count = binr.ReadByte(); // data size in next byte
else
if(bt == 0x82) {
highbyte = binr.ReadByte(); // data size in next 2 bytes
lowbyte = binr.ReadByte();
byte[] modint = {lowbyte, highbyte, 0x00, 0x00} ;
count = BitConverter.ToInt32(modint, 0) ;
}
else {
count = bt; // we already have the data size
}
while(binr.ReadByte() == 0x00) { //remove high order zeros in data
count -=1;
}
binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte
return count;
}
//----- Get the binary RSA PRIVATE key, decrypting if necessary ----
public static byte[] DecodeOpenSSLPrivateKey(String instr)
{
const String pemprivheader = "-----BEGIN RSA PRIVATE KEY-----" ;
const String pemprivfooter = "-----END RSA PRIVATE KEY-----" ;
String pemstr = instr.Trim() ;
byte[] binkey;
if(!pemstr.StartsWith(pemprivheader) || !pemstr.EndsWith(pemprivfooter))
return null;
StringBuilder sb = new StringBuilder(pemstr) ;
sb.Replace(pemprivheader, "") ; //remove headers/footers, if present
sb.Replace(pemprivfooter, "") ;
String pvkstr = sb.ToString().Trim(); //get string after removing leading/trailing whitespace
try{ // if there are no PEM encryption info lines, this is an UNencrypted PEM private key
binkey = Convert.FromBase64String(pvkstr) ;
return binkey;
}
catch(System.FormatException) { //if can't b64 decode, it must be an encrypted private key
//Console.WriteLine("Not an unencrypted OpenSSL PEM private key");
}
StringReader str = new StringReader(pvkstr);
//-------- read PEM encryption info. lines and extract salt -----
if(!str.ReadLine().StartsWith("Proc-Type: 4,ENCRYPTED"))
return null;
String saltline = str.ReadLine();
if(!saltline.StartsWith("DEK-Info: DES-EDE3-CBC,") )
return null;
String saltstr = saltline.Substring(saltline.IndexOf(",") + 1).Trim() ;
byte[] salt = new byte[saltstr.Length/2];
for (int i=0; i <salt.Length; i++)
salt[i] = Convert.ToByte(saltstr.Substring (i*2, 2), 16);
if(! (str.ReadLine() == ""))
return null;
//------ remaining b64 data is encrypted RSA key ----
String encryptedstr = str.ReadToEnd() ;
try{ //should have b64 encrypted RSA key now
binkey = Convert.FromBase64String(encryptedstr) ;
}
catch(System.FormatException) { // bad b64 data.
return null;
}
//------ Get the 3DES 24 byte key using PDK used by OpenSSL ----
SecureString despswd = GetSecPswd("Enter password to derive 3DES key==>") ;
//Console.Write("\nEnter password to derive 3DES key: ");
//String pswd = Console.ReadLine();
byte[] deskey = GetOpenSSL3deskey(salt, despswd, 1, 2); // count=1 (for OpenSSL implementation); 2 iterations to get at least 24 bytes
if(deskey == null)
return null;
//showBytes("3DES key", deskey) ;
//------ Decrypt the encrypted 3des-encrypted RSA private key ------
byte[] rsakey = DecryptKey(binkey, deskey, salt); //OpenSSL uses salt value in PEM header also as 3DES IV
if(rsakey !=null)
return rsakey; //we have a decrypted RSA private key
else {
Console.WriteLine("Failed to decrypt RSA private key; probably wrong password.");
return null;
}
}
// ----- Decrypt the 3DES encrypted RSA private key ----------
public static byte[] DecryptKey(byte[] cipherData, byte[] desKey, byte[] IV)
{
MemoryStream memst = new MemoryStream();
TripleDES alg = TripleDES.Create();
alg.Key = desKey;
alg.IV = IV;
try{
CryptoStream cs = new CryptoStream(memst, alg.CreateDecryptor(), CryptoStreamMode.Write);
cs.Write(cipherData, 0, cipherData.Length);
cs.Close();
}
catch(Exception exc){
Console.WriteLine(exc.Message);
return null ;}
byte[] decryptedData = memst.ToArray();
return decryptedData;
}
//----- OpenSSL PBKD uses only one hash cycle (count); miter is number of iterations required to build sufficient bytes ---
private static byte[] GetOpenSSL3deskey(byte[] salt, SecureString secpswd, int count, int miter ) {
IntPtr unmanagedPswd = IntPtr.Zero;
int HASHLENGTH = 16; //MD5 bytes
byte[] keymaterial = new byte[HASHLENGTH*miter] ; //to store contatenated Mi hashed results
byte[] psbytes = new byte[secpswd.Length] ;
unmanagedPswd = Marshal.SecureStringToGlobalAllocAnsi(secpswd);
Marshal.Copy(unmanagedPswd, psbytes, 0, psbytes.Length) ;
Marshal.ZeroFreeGlobalAllocAnsi(unmanagedPswd);
//UTF8Encoding utf8 = new UTF8Encoding();
//byte[] psbytes = utf8.GetBytes(pswd);
// --- contatenate salt and pswd bytes into fixed data array ---
byte[] data00 = new byte[psbytes.Length + salt.Length] ;
Array.Copy(psbytes, data00, psbytes.Length); //copy the pswd bytes
Array.Copy(salt, 0, data00, psbytes.Length, salt.Length) ; //concatenate the salt bytes
// ---- do multi-hashing and contatenate results D1, D2 ... into keymaterial bytes ----
MD5 md5 = new MD5CryptoServiceProvider();
byte[] result = null;
byte[] hashtarget = new byte[HASHLENGTH + data00.Length]; //fixed length initial hashtarget
for(int j=0; j<miter; j++)
{
// ---- Now hash consecutively for count times ------
if(j == 0)
result = data00; //initialize
else {
Array.Copy(result, hashtarget, result.Length);
Array.Copy(data00, 0, hashtarget, result.Length, data00.Length) ;
result = hashtarget;
//Console.WriteLine("Updated new initial hash target:") ;
//showBytes(result) ;
}
for(int i=0; i<count; i++)
result = md5.ComputeHash(result);
Array.Copy(result, 0, keymaterial, j*HASHLENGTH, result.Length); //contatenate to keymaterial
}
//showBytes("Final key material", keymaterial);
byte[] deskey = new byte[24];
Array.Copy(keymaterial, deskey, deskey.Length) ;
Array.Clear(psbytes, 0, psbytes.Length);
Array.Clear(data00, 0, data00.Length) ;
Array.Clear(result, 0, result.Length) ;
Array.Clear(hashtarget, 0, hashtarget.Length) ;
Array.Clear(keymaterial, 0, keymaterial.Length) ;
return deskey;
}
//------ Since we are using an RSA with nonpersisted keycontainer, must pass it in to ensure it isn't colledted -----
private static byte[] GetPkcs12(RSA rsa, String keycontainer, String cspprovider, uint KEYSPEC, uint cspflags)
{
byte[] pfxblob = null;
IntPtr hCertCntxt = IntPtr.Zero;
String DN = "CN=Opensslkey Unsigned Certificate";
hCertCntxt = CreateUnsignedCertCntxt(keycontainer, cspprovider, KEYSPEC, cspflags, DN) ;
if(hCertCntxt == IntPtr.Zero){
Console.WriteLine("Couldn't create an unsigned-cert\n") ;
return null;
}
try{
X509Certificate cert = new X509Certificate(hCertCntxt) ; //create certificate object from cert context.
X509Certificate2UI.DisplayCertificate(new X509Certificate2(cert)) ; // display it, showing linked private key
SecureString pswd = GetSecPswd("Set PFX Password ==>") ;
pfxblob = cert.Export(X509ContentType.Pkcs12, pswd);
}
catch(Exception exc)
{
Console.WriteLine( "BAD RESULT" + exc.Message);
pfxblob = null;
}
rsa.Clear() ;
if(hCertCntxt != IntPtr.Zero)
Win32.CertFreeCertificateContext(hCertCntxt) ;
return pfxblob;
}
private static IntPtr CreateUnsignedCertCntxt(String keycontainer, String provider, uint KEYSPEC, uint cspflags, String DN) {
const uint AT_KEYEXCHANGE = 0x00000001;
const uint AT_SIGNATURE = 0x00000002;
const uint CRYPT_MACHINE_KEYSET = 0x00000020;
const uint PROV_RSA_FULL = 0x00000001;
const String MS_DEF_PROV = "Microsoft Base Cryptographic Provider v1.0";
const String MS_STRONG_PROV = "Microsoft Strong Cryptographic Provider";
const String MS_ENHANCED_PROV = "Microsoft Enhanced Cryptographic Provider v1.0";
const uint CERT_CREATE_SELFSIGN_NO_SIGN = 1 ;
const uint X509_ASN_ENCODING = 0x00000001;
const uint CERT_X500_NAME_STR = 3;
IntPtr hCertCntxt = IntPtr.Zero;
byte[] encodedName = null;
uint cbName = 0;
if( provider != MS_DEF_PROV && provider != MS_STRONG_PROV && provider != MS_ENHANCED_PROV)
return IntPtr.Zero;
if(keycontainer == "")
return IntPtr.Zero;
if( KEYSPEC != AT_SIGNATURE && KEYSPEC != AT_KEYEXCHANGE)
return IntPtr.Zero;
if(cspflags != 0 && cspflags != CRYPT_MACHINE_KEYSET) //only 0 (Current User) keyset is currently used.
return IntPtr.Zero;
if (DN == "")
return IntPtr.Zero;
if(Win32.CertStrToName(X509_ASN_ENCODING, DN, CERT_X500_NAME_STR, IntPtr.Zero, null, ref cbName, IntPtr.Zero))
{
encodedName = new byte[cbName] ;
Win32.CertStrToName(X509_ASN_ENCODING, DN, CERT_X500_NAME_STR, IntPtr.Zero, encodedName, ref cbName, IntPtr.Zero);
}
CERT_NAME_BLOB subjectblob = new CERT_NAME_BLOB();
subjectblob.pbData = Marshal.AllocHGlobal(encodedName.Length);
Marshal.Copy(encodedName, 0, subjectblob.pbData, encodedName.Length);
subjectblob.cbData = encodedName.Length;
CRYPT_KEY_PROV_INFO pInfo = new CRYPT_KEY_PROV_INFO();
pInfo.pwszContainerName = keycontainer;
pInfo.pwszProvName = provider;
pInfo.dwProvType = PROV_RSA_FULL;
pInfo.dwFlags = cspflags;
pInfo.cProvParam = 0;
pInfo.rgProvParam = IntPtr.Zero;
pInfo.dwKeySpec = KEYSPEC;
hCertCntxt = Win32.CertCreateSelfSignCertificate(IntPtr.Zero, ref subjectblob, CERT_CREATE_SELFSIGN_NO_SIGN, ref pInfo, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
if(hCertCntxt == IntPtr.Zero)
showWin32Error(Marshal.GetLastWin32Error());
Marshal.FreeHGlobal(subjectblob.pbData);
return hCertCntxt ;
}
private static SecureString GetSecPswd(String prompt)
{
SecureString password = new SecureString();
Console.ForegroundColor = ConsoleColor.Gray;
Console.Write(prompt);
Console.ForegroundColor = ConsoleColor.Magenta;
while (true)
{
ConsoleKeyInfo cki = Console.ReadKey(true);
if (cki.Key == ConsoleKey.Enter)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (cki.Key == ConsoleKey.Backspace)
{
// remove the last asterisk from the screen...
if (password.Length > 0)
{
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
Console.Write(" ");
Console.SetCursorPosition(Console.CursorLeft - 1, Console.CursorTop);
password.RemoveAt(password.Length - 1);
}
}
else if (cki.Key == ConsoleKey.Escape)
{
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine();
return password;
}
else if (Char.IsLetterOrDigit(cki.KeyChar) || Char.IsSymbol(cki.KeyChar))
{
if (password.Length < 20)
{
password.AppendChar(cki.KeyChar);
Console.Write("*");
}
else
{
Console.Beep();
}
}
else
{
Console.Beep();
}
}
}
private static bool CompareBytearrays(byte [] a, byte[] b)
{
if(a.Length != b.Length)
return false;
int i =0;
foreach(byte c in a)
{
if(c != b[i] )
return false;
i++;
}
return true;
}
private static void showRSAProps(RSACryptoServiceProvider rsa) {
Console.WriteLine("RSA CSP key information:");
CspKeyContainerInfo keyInfo = rsa.CspKeyContainerInfo;
Console.WriteLine("Accessible property: " + keyInfo.Accessible);
Console.WriteLine("Exportable property: " + keyInfo.Exportable);
Console.WriteLine("HardwareDevice property: " + keyInfo.HardwareDevice);
Console.WriteLine("KeyContainerName property: " + keyInfo.KeyContainerName);
Console.WriteLine("KeyNumber property: " + keyInfo.KeyNumber.ToString());
Console.WriteLine("MachineKeyStore property: " + keyInfo.MachineKeyStore);
Console.WriteLine("Protected property: " + keyInfo.Protected);
Console.WriteLine("ProviderName property: " + keyInfo.ProviderName);
Console.WriteLine("ProviderType property: " + keyInfo.ProviderType);
Console.WriteLine("RandomlyGenerated property: " + keyInfo.RandomlyGenerated);
Console.WriteLine("Removable property: " + keyInfo.Removable);
Console.WriteLine("UniqueKeyContainerName property: " + keyInfo.UniqueKeyContainerName);
}
private static void showBytes(String info, byte[] data){
Console.WriteLine("{0} [{1} bytes]", info, data.Length);
for(int i=1; i<=data.Length; i++){
Console.Write("{0:X2} ", data[i-1]) ;
if(i%16 == 0)
Console.WriteLine();
}
Console.WriteLine("\n\n");
}
private static byte[] GetFileBytes(String filename){
if(!File.Exists(filename))
return null;
Stream stream=new FileStream(filename,FileMode.Open);
int datalen = (int)stream.Length;
byte[] filebytes =new byte[datalen];
stream.Seek(0,SeekOrigin.Begin);
stream.Read(filebytes,0,datalen);
stream.Close();
return filebytes;
}
private static void PutFileBytes(String outfile, byte[] data, int bytes) {
FileStream fs = null;
if(bytes > data.Length) {
Console.WriteLine("Too many bytes");
return;
}
try{
fs = new FileStream(outfile, FileMode.Create);
fs.Write(data, 0, bytes);
}
catch(Exception e) {
Console.WriteLine(e.Message) ;
}
finally {
fs.Close();
}
}
private static void showWin32Error(int errorcode)
{
Win32Exception myEx=new Win32Exception(errorcode);
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("Error code:\t 0x{0:X}", myEx.ErrorCode);
Console.WriteLine("Error message:\t {0}\n", myEx.Message);
Console.ForegroundColor = ConsoleColor.Gray;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment