Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / security / encryption

Open .key and .cer files, sign string with private key

4.31/5 (13 votes)
13 Jan 2014CPOL 81.4K  
Specific implemetation for Mexico CFDI. others can use as they need

Introduction 

This portion of code covers the signing of original string and get the certificate data from Mexico FIEL (package of .key file, .cer file and password for .key file) to build electronic invoices. Also, can be used with other generic purposes, but it only covers encryption of Mexico files. to check other encryption formats, you can check the full code on which this implementation is based at http://www.jensign.com/opensslkey/opensslkey.cs

Background 

I have a nightmare finding correct functions to get the electronic invoices signed, many developers recommend to use openssl.exe to obtain the signed string for electronic invoices, that option has the inconvenience of compatibility between 32b and 64b operating systems, and requires a separate setup. This piece of code cover that specific issue. Note to my fellow countrymen, this code does not generate the original string (cadena original) nor the XML file.

Using the code 

For specific implentation mentioned, you only need to use the SignString(string pKeyFile,string pPassword,string OriginalString) function, on which

pKeyFile=fullpath .key file

pPassword=password of .key file

OriginalString=string to be signed.

Function returns signed string according with the parameters 

C#
//**********************************************************************************
//Implementacion de librerias para sellar la cadena original SAT factura electronica CFDI
//Son necesarios los archivo .key, .cer y el password proporcionados por el SAT 
//al contribuyente, que en su conjunto se conoce como FIEL.
//
//Juan Antonio Lopez
//H. Matamoros, Tamaulipas, Mexico
//antoniolopezrmx@me.com
//*****NOTA****
//Este codigo no genera la cadena original ni el archivo XML
//***************BASADO EN LA IMPLEMENTACION COMPLETA*******************************
//OpenSSLKey
// .NET 2.0  OpenSSL Public & Private Key Parser
//
// Copyright (C) 2008  	JavaScience Consulting
//
//***********************************************************************************
//  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 OpenSSL
{
     public class opensslkey
    {
        
        public string SignString(string pKeyFile,string pPassword,string OriginalString)
        {
            string SignedString = "";
            string filename = pKeyFile;
            if (!File.Exists(filename))
            {
                return ".key file does not exist " + pKeyFile;
            }

            RSACryptoServiceProvider rsa=OpenKeyFile(filename, pPassword);
            if (rsa != null)
            {
                byte[] CO=Encoding.UTF8.GetBytes(OriginalString);
                byte[] SignedBytes=rsa.SignData(CO, new SHA1CryptoServiceProvider());
                SignedString = Convert.ToBase64String(SignedBytes);
            }
            return SignedString;
        }

        public RSACryptoServiceProvider OpenKeyFile(String filename,string pPassword)
        {
            RSACryptoServiceProvider rsa = null;
            byte[] keyblob = GetFileBytes(filename);
            if (keyblob == null)
                return null;

            rsa = DecodePrivateKeyInfo(keyblob, pPassword);	//PKCS #8 encrypted
            if (rsa != null)
            {
                return rsa;
            }
            return null;
        }

        public static RSACryptoServiceProvider 
                  DecodePrivateKeyInfo(byte[] encpkcs8,string pPassword)
        {
            // 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);

                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;

                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);

                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);
                SecureString secpswd = new SecureString();
                foreach (char c in pPassword)
                    secpswd.AppendChar(c);

                pkcs8 = DecryptPBDK2(encryptedpkcs8, salt, IV, secpswd, iterations);
                if (pkcs8 == null)	// probably a bad pswd entered.
                    return null;

                RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8);
                return rsa;
            }

            catch (Exception)
            {
                return null;
            }

            finally { binr.Close(); }


        }

        public void CertificateData(string pCerFile, out string Certificate, out string CertificateNumber)
        {
            X509Certificate cert = new X509Certificate(pCerFile);
            byte[] strcert = cert.GetRawCertData();
            Certificate = Convert.ToBase64String(strcert);

            strcert = cert.GetSerialNumber();
            CertificateNumber = Reverse(System.Text.Encoding.UTF8.GetString(strcert));
        }

        public string Reverse(string Original)
        {
            string Reverse = "";
            for (int i = Original.Length - 1; i >= 0; i--)
                Reverse += Original.Substring(i, 1);
            return Reverse;
        }

        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 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;
        }

        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;
            }
        }

        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(); }
        }

        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 ..");

                // ------- 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;
        }

    }
}

//using code
public void Test()
{
    OpenSSL.opensslkey libssl = new OpenSSL.opensslkey();
    string SignedString = libssl.SignString(@"c:\fiel\aaaa121213123123aaa_t.key", 
      "0123456789", "||3.2|test|cadena|original|");
    string Certificate = "";
    string CertificateNumber = "";
    libssl.CertificateData(@"c:\fiel\aaaa121213123123aaa_t.cer", out Certificate, out CertificateNumber);

    /***Results are fictional data
     SignedString      ="IezD44V16UyFh5Jpf/Xv2uytMqGv1eOJ+Cr8NdhoETp2pQHu+2108XQJGU1x4s82AQ1NGlIjqUuKey7F4Q7Hkur4MhvjUpSqJtJRfDAbP2lWdmSzbZPx7Sv3LP0YZBP10mmk4Kre7wSod0A/oLqv2GZh+kvK2xPx9v13y14T9Js="
C#
Certificate       ="CgfhC02gAwIBAgIUMDAwMDEwM7AwMDAyMD9Iyjg3zQwDYJKoIhcNAQEFBQAwggGVMTgwNgYDVQQDDC9BLkMuIGRlbCBTZXJ2aWNpbyBkZSBBZG1pbmlzdHJhY2nDs24gVHJpYnV0YXJpYTEvMC0GA1UECgwmU2VydmljaW8gZGUgQWRtaW5pc3RyYWNpw7NuIFRyaWJ1dGFyaWExODA2BgNVBAsML0FkbWluaXN0cmFjacOzbiBkZSBTZWd1cmlkYWQgZGUgbGEgSW5mb3JtYWNpw7NuMSEwHwYJKoZIhvcNAQkBFhJhc2lzbmV0QHNhdC5nb2IubXgxJjAkBgNVBAkMHUF2LiBIaWRhbGdvIDc3LCBDb2wuIEd1ZXJyZXJvMQ4wDAYDVQQRDAUwNjMwMDELMAkGA1UEBhMCTVgxGTAXBgNVBAgMEERpc3RyaXRvIEZlZGVyYWwxFDASBgNVBAcMC0N1YXVodMOpbW9jMRUwEwYDVQQtEwxTQVQ5NzA3MDFOTjMxPjA8BgkqhkiG9w0BCQIML1Jlc3BvbnNhYmxlOiBDZWNpbGlhIEd1aWxsZXJtaW5hIEdhcmPDrWEgR3VlcnJhMB4XDTEyMTEwMzEzMDAzN1oXDTE2MTEwMzEzMDAzN1owgaYxHjAcBgNVBAMTFURBTUlBTiBQRVJFWiBNQVJUSU5FWjEeMBwGA1UEKRMVREFNSUFOIFBFUkVaIE1BUlRJTkVaMR4wHAYDVQQKExVEQU1JQU4gUEVSRVogTUFSVElORVoxFjAUBgNVBC0TDVBFTUQ3MzA3MjQyMTQxGzAZBgNVBAUTElBFTUQ3MzA3MjRIVFNSUk0wOTEPMA0GA1UECxMGVW5pZGFkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyvvaw+0P7kMbpzMiYG7IrX/lb3tJkvDIAgWCnIid2mdyjaPrMVi/2aZrohvnUZjaYUlbxFWhLBJcXEdsZpT3QAF+JahWqIzkg2PLk5MHDdncOJI+s/26yNs+efjOlWXa2uS8Q+9M9ZxOWLOMX6sLSTupdR8pvvgylQXjMm3L6RQIDAQABox0wGzAMBgNVHRMBAf8EAjAAMAsGA1UdDwQEAwIGwDANBgkqhkiG9w0BAQUFAAOCAQEATOqpxj3d1iFJx/OG4Jlg8WYG7JJiGK8b0Yi7Gh7YXf03cJQBLsbEj UEunO04s3Tl/gwoglrQ/5uvYeswoin4zq6Sj6MOFb9mFUfDRT8p9VKm19dW/IWjFfgVgaS5OWoMK3X0/EkrnVdz7nb2q0f5/icbUbSvNu/6wBo6FS53qqHNL3DF0sK4KbUJCgrpmdv0/0Rp8M8ezYNEqqNMf/tqHN9EV5hf02M7z1pyL6Dg0Aj4NkWTdWDqWe/fLTeNcUFNDJCZFBMTn83t4z8G9aRHRlNepepSeRX5AvQc6W+iTclU0GM33w=="
C#
     CertificateNumber ="00009000000822643513"
     */
} 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)