.NET RSACryptoServiceProvider PEM + DER Support
http://www.christian-etter.de/?p=771
In .NET, RSACryptoServiceProvider greatly simplifies common tasks
associated with public/private keys, such as signing of data and verifying
a signature.
Unfortunately, there is only a single format (proprietary
XML) available for importing and exporting public/private key data.
The
two widely spread formats for key exchange, PEM and DER are not supported,
which limits the usability of the class when working with different kinds
of public key APIs.
There are a few workarounds though:
- Write your own .NET code to support the desired formats, which might not be that simple, as the following blog post suggests.
- Use a third party component, such as the PEMReader class of the Bouncy Castle Library (Java/C# port).
- Leverage Microsoft Crypto API (CAPI), which provides support for opening and converting a variety of different public/private key file formats. Here I would like to demonstrate how this can be achieved by means of simple extension methods to theRSACryptoServiceProvider class.
Here is a sample RSA 1024 bit private key in PEM format:
-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDwIqfvxEjqHu8048x4wJ5EId6ASAbWdH5fzgHxvew5kXqECMNc XzRqDVnDVPQT41UeZs8HxouBE+ZA8DfnVlHwP4EIeigOUaqy0sseKpO71tupFU+2 LjpcF6O7cVuLjt6476iYfSyrssK4hnmzVYGZNz16OSR9z/SuTd8BhohG4QIDAQAB AoGBAOmEmhEUrN9XU8D4IVfv4DhbQ1c2M8gKovYhjEx8J6LX8O9C4lAKmRrkfrzv +Sb59EVLLtrd3b2ZD1lpAMQrciMwC5PAa8da/J++lR1VjM5GbzqKjGtfx3WQlzNE 1ZaZ2FSY8lAPMM4uLczyD79PJQBsGCcx3KDJRR5ENp6an5cRAkEA/m1FEqol/KKh xOyGsK4GVuansBXhrAgpwMlYLT+vF0gy1jzYQDNNQXzeQFYH6gZY66RTYFl3JPNL 8KXLyhwDLQJBAPGew6xkLBoYi4IO9I+NP/gIHzSiQeEl2OxZsgZiz0Yh5E9ndwMr 87jTX/4ZBwNlDC0E+MXsJpMSvTFNpw4rcwUCQQC5FU5JLKOjq79YnOPChWYxM2vL Ka/YULvm9dGCYTCDFE9/EBYUZf2OZULctHjfYqyvBwRsM8j7hU26CzI7nbMlAkAA kVjwXMPlw80AHzzf4XsXAB3ip8bz2nzqAUPz0+OczJOWxC15am8GLij5leF4VpJy wKI9BNMKYW7kYMRVujBpAkEA7gQ8MGqjjrCAfOzrrC9ZuVdGRfEjUEdHMqiF+js7 XNBvnT5lBznUOd+eta6CGo7S5hjU7D3CEzmVGQfxUsRZ1w== -----END RSA PRIVATE KEY-----
This is the corresponding public key in textual PEM representation:
-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDwIqfvxEjqHu8048x4wJ5EId6A SAbWdH5fzgHxvew5kXqECMNcXzRqDVnDVPQT41UeZs8HxouBE+ZA8DfnVlHwP4EI eigOUaqy0sseKpO71tupFU+2LjpcF6O7cVuLjt6476iYfSyrssK4hnmzVYGZNz16 OSR9z/SuTd8BhohG4QIDAQAB -----END PUBLIC KEY-----
We cannot load these keys into the RSACryptoServiceProvider directly.
However the class supports an import method which is compatible with
CryptoAPI: ImportCspBlob().
With CAPI in return, we can do all
the heavy lifting including format conversion which the .NET Framework
does not support.
For public keys, the conversion process for PEM
(string) key data requires the following steps:
- Converting a public key in PEM string format into DER
representation.
CryptStringToBinaryA( sPEM, (UInt32)sPEM.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, IntPtr.Zero, ref dwBinarySize, out dwSkip, out dwFlags ) )
- Retrieve the key through a CERT_PUBLIC_KEY_INFO struct.
CryptDecodeObjectEx( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.X509_PUBLIC_KEY_INFO ), DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.CRYPT_DECODE_ALLOC_FLAG, IntPtr.Zero, ref pCertPublicKeyInfo, out dwCertPublicKeyInfoSize )
- Convert the returned RSA key into a Diffie-Hellman Version 3 Public Key BLOBs or DSS Version
3 Public Key BLOBs struct:
CryptDecodeObjectEx( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.RSA_CSP_PUBLICKEYBLOB ), RSAData, (UInt32)RSAData.Length, CRYPT_DECODE_FLAGS.CRYPT_DECODE_ALLOC_FLAG, IntPtr.Zero, ref pCertPublicKeyBlob, out dwCertPublicKeyBlobSize )
- Call RSACryptoServiceProvider.ImportCspBlob() for the resulting binary data.
For private keys, the following steps are required:
- Converting a private key in PEM string format into DER
representation.
CryptStringToBinaryA( sPEM, (UInt32)sPEM.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, IntPtr.Zero, ref dwBinarySize, out dwSkip, out dwFlags ) )
- For private keys, retrieve a pointer to an RSA private key BLOB directly:
CryptDecodeObjectEx( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.PKCS_RSA_PRIVATE_KEY ), DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.CRYPT_DECODE_ALLOC_FLAG, IntPtr.Zero, ref pRSAPrivateKeyBlob, out pRSAPrivateKeyBlobSize )
- Call RSACryptoServiceProvider.ImportCspBlob() for the resulting binary data.
If you wrap the above logic into extension methods for RSACryptoServiceProvider, importing keys in PEM format and signing data can be done with a few lines of code:
// -----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY----- string sPrivateKeyPEM = File.ReadAllText( "PrivateKey.pem" ); using ( RSACryptoServiceProvider rsa = new RSACryptoServiceProvider() ) { rsa.PersistKeyInCsp = false; rsa.LoadPrivateKeyPEM( sPrivateKeyPEM ); using ( SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider() ) signature = rsa.SignData( dataToSign, sha1 ); } |
Verifying the same signature also becomes easy:
// -----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY----- string sPublicKeyPEM = File.ReadAllText( "PublicKey.pem" ); using ( RSACryptoServiceProvider rsa = new RSACryptoServiceProvider() ) { rsa.PersistKeyInCsp = false; rsa.LoadPublicKeyPEM( sPublicKeyPEM ); using ( SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider() ) bVerifyResultOriginal = rsa.VerifyData( dataToSign, sha1, signature ); } |
Here is a cut-and-paste solution based on an extension class to RSACryptoServiceProvider:
/********************************************************************************* * Copyright (c) 2013, Christian Etter info at christian-etter dot de * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *********************************************************************************/ using System; using System.ComponentModel; using System.Runtime.InteropServices; using System.Security.Cryptography; /// <summary>Extension method for initializing a RSACryptoServiceProvider from PEM data string.</summary> public static class RSACryptoServiceProviderExtension { #region Methods /// <summary>Extension method which initializes an RSACryptoServiceProvider from a DER public key blob.</summary> public static void LoadPublicKeyDER( this RSACryptoServiceProvider provider, byte[] DERData ) { byte[] RSAData = RSACryptoServiceProviderExtension.GetRSAFromDER( DERData ); byte[] publicKeyBlob = RSACryptoServiceProviderExtension.GetPublicKeyBlobFromRSA( RSAData ); provider.ImportCspBlob( publicKeyBlob ); } /// <summary>Extension method which initializes an RSACryptoServiceProvider from a DER private key blob.</summary> public static void LoadPrivateKeyDER( this RSACryptoServiceProvider provider, byte[] DERData ) { byte[] privateKeyBlob = RSACryptoServiceProviderExtension.GetPrivateKeyDER( DERData ); provider.ImportCspBlob( privateKeyBlob ); } /// <summary>Extension method which initializes an RSACryptoServiceProvider from a PEM public key string.</summary> public static void LoadPublicKeyPEM( this RSACryptoServiceProvider provider, string sPEM ) { byte[] DERData = RSACryptoServiceProviderExtension.GetDERFromPEM( sPEM ); RSACryptoServiceProviderExtension.LoadPublicKeyDER( provider, DERData ); } /// <summary>Extension method which initializes an RSACryptoServiceProvider from a PEM private key string.</summary> public static void LoadPrivateKeyPEM( this RSACryptoServiceProvider provider, string sPEM ) { byte[] DERData = RSACryptoServiceProviderExtension.GetDERFromPEM( sPEM ); RSACryptoServiceProviderExtension.LoadPrivateKeyDER( provider, DERData ); } /// <summary>Returns a public key blob from an RSA public key.</summary> internal static byte[] GetPublicKeyBlobFromRSA( byte[] RSAData ) { byte[] data = null; UInt32 dwCertPublicKeyBlobSize = 0; if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.RSA_CSP_PUBLICKEYBLOB ), RSAData, (UInt32)RSAData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyBlobSize ) ) { data = new byte[ dwCertPublicKeyBlobSize ]; if ( !RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.RSA_CSP_PUBLICKEYBLOB ), RSAData, (UInt32)RSAData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyBlobSize ) ) throw new Win32Exception( Marshal.GetLastWin32Error() ); } else throw new Win32Exception( Marshal.GetLastWin32Error() ); return data; } /// <summary>Converts DER binary format to a CAPI CRYPT_PRIVATE_KEY_INFO structure.</summary> internal static byte[] GetPrivateKeyDER( byte[] DERData ) { byte[] data = null; UInt32 dwRSAPrivateKeyBlobSize = 0; IntPtr pRSAPrivateKeyBlob = IntPtr.Zero; if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.PKCS_RSA_PRIVATE_KEY ), DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwRSAPrivateKeyBlobSize ) ) { data = new byte[ dwRSAPrivateKeyBlobSize ]; if ( !RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.PKCS_RSA_PRIVATE_KEY ), DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwRSAPrivateKeyBlobSize ) ) throw new Win32Exception( Marshal.GetLastWin32Error() ); } else throw new Win32Exception( Marshal.GetLastWin32Error() ); return data; } /// <summary>Converts DER binary format to a CAPI CERT_PUBLIC_KEY_INFO structure containing an RSA key.</summary> internal static byte[] GetRSAFromDER( byte[] DERData ) { byte[] data = null; byte[] publicKey = null; CERT_PUBLIC_KEY_INFO info; UInt32 dwCertPublicKeyInfoSize = 0; IntPtr pCertPublicKeyInfo = IntPtr.Zero; if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.X509_PUBLIC_KEY_INFO ), DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyInfoSize ) ) { data = new byte[ dwCertPublicKeyInfoSize ]; if ( RSACryptoServiceProviderExtension.CryptDecodeObject( CRYPT_ENCODING_FLAGS.X509_ASN_ENCODING | CRYPT_ENCODING_FLAGS.PKCS_7_ASN_ENCODING, new IntPtr( (int)CRYPT_OUTPUT_TYPES.X509_PUBLIC_KEY_INFO ), DERData, (UInt32)DERData.Length, CRYPT_DECODE_FLAGS.NONE, data, ref dwCertPublicKeyInfoSize ) ) { GCHandle handle = GCHandle.Alloc( data, GCHandleType.Pinned ); try { info = (CERT_PUBLIC_KEY_INFO)Marshal.PtrToStructure( handle.AddrOfPinnedObject(), typeof( CERT_PUBLIC_KEY_INFO ) ); publicKey = new byte[ info.PublicKey.cbData ]; Marshal.Copy( info.PublicKey.pbData, publicKey, 0, publicKey.Length ); } finally { handle.Free(); } } else throw new Win32Exception( Marshal.GetLastWin32Error() ); } else throw new Win32Exception( Marshal.GetLastWin32Error() ); return publicKey; } /// <summary>Extracts the binary data from a PEM file.</summary> internal static byte[] GetDERFromPEM( string sPEM ) { UInt32 dwSkip, dwFlags; UInt32 dwBinarySize = 0; if ( !RSACryptoServiceProviderExtension.CryptStringToBinary( sPEM, (UInt32)sPEM.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, null, ref dwBinarySize, out dwSkip, out dwFlags ) ) throw new Win32Exception( Marshal.GetLastWin32Error() ); byte[] decodedData = new byte[ dwBinarySize ]; if ( !RSACryptoServiceProviderExtension.CryptStringToBinary( sPEM, (UInt32)sPEM.Length, CRYPT_STRING_FLAGS.CRYPT_STRING_BASE64HEADER, decodedData, ref dwBinarySize, out dwSkip, out dwFlags ) ) throw new Win32Exception( Marshal.GetLastWin32Error() ); return decodedData; } #endregion Methods #region P/Invoke Constants /// <summary>Enumeration derived from Crypto API.</summary> internal enum CRYPT_ACQUIRE_CONTEXT_FLAGS : uint { CRYPT_NEWKEYSET = 0x8, CRYPT_DELETEKEYSET = 0x10, CRYPT_MACHINE_KEYSET = 0x20, CRYPT_SILENT = 0x40, CRYPT_DEFAULT_CONTAINER_OPTIONAL = 0x80, CRYPT_VERIFYCONTEXT = 0xF0000000 } /// <summary>Enumeration derived from Crypto API.</summary> internal enum CRYPT_PROVIDER_TYPE : uint { PROV_RSA_FULL = 1 } /// <summary>Enumeration derived from Crypto API.</summary> internal enum CRYPT_DECODE_FLAGS : uint { NONE = 0, CRYPT_DECODE_ALLOC_FLAG = 0x8000 } /// <summary>Enumeration derived from Crypto API.</summary> internal enum CRYPT_ENCODING_FLAGS : uint { PKCS_7_ASN_ENCODING = 0x00010000, X509_ASN_ENCODING = 0x00000001, } /// <summary>Enumeration derived from Crypto API.</summary> internal enum CRYPT_OUTPUT_TYPES : int { X509_PUBLIC_KEY_INFO = 8, RSA_CSP_PUBLICKEYBLOB = 19, PKCS_RSA_PRIVATE_KEY = 43, PKCS_PRIVATE_KEY_INFO = 44 } /// <summary>Enumeration derived from Crypto API.</summary> internal enum CRYPT_STRING_FLAGS : uint { CRYPT_STRING_BASE64HEADER = 0, CRYPT_STRING_BASE64 = 1, CRYPT_STRING_BINARY = 2, CRYPT_STRING_BASE64REQUESTHEADER = 3, CRYPT_STRING_HEX = 4, CRYPT_STRING_HEXASCII = 5, CRYPT_STRING_BASE64_ANY = 6, CRYPT_STRING_ANY = 7, CRYPT_STRING_HEX_ANY = 8, CRYPT_STRING_BASE64X509CRLHEADER = 9, CRYPT_STRING_HEXADDR = 10, CRYPT_STRING_HEXASCIIADDR = 11, CRYPT_STRING_HEXRAW = 12, CRYPT_STRING_NOCRLF = 0x40000000, CRYPT_STRING_NOCR = 0x80000000 } #endregion P/Invoke Constants #region P/Invoke Structures /// <summary>Structure from Crypto API.</summary> [StructLayout( LayoutKind.Sequential )] internal struct CRYPT_OBJID_BLOB { internal UInt32 cbData; internal IntPtr pbData; } /// <summary>Structure from Crypto API.</summary> [StructLayout( LayoutKind.Sequential )] internal struct CRYPT_ALGORITHM_IDENTIFIER { internal IntPtr pszObjId; internal CRYPT_OBJID_BLOB Parameters; } /// <summary>Structure from Crypto API.</summary> [StructLayout( LayoutKind.Sequential )] struct CRYPT_BIT_BLOB { internal UInt32 cbData; internal IntPtr pbData; internal UInt32 cUnusedBits; } /// <summary>Structure from Crypto API.</summary> [StructLayout( LayoutKind.Sequential )] struct CERT_PUBLIC_KEY_INFO { internal CRYPT_ALGORITHM_IDENTIFIER Algorithm; internal CRYPT_BIT_BLOB PublicKey; } #endregion P/Invoke Structures #region P/Invoke Functions /// <summary>Function for Crypto API.</summary> [DllImport( "advapi32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CryptDestroyKey( IntPtr hKey ); /// <summary>Function for Crypto API.</summary> [DllImport( "advapi32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CryptImportKey( IntPtr hProv, byte[] pbKeyData, UInt32 dwDataLen, IntPtr hPubKey, UInt32 dwFlags, ref IntPtr hKey ); /// <summary>Function for Crypto API.</summary> [DllImport( "advapi32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CryptReleaseContext( IntPtr hProv, Int32 dwFlags ); /// <summary>Function for Crypto API.</summary> [DllImport( "advapi32.dll", CharSet = CharSet.Auto, SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CryptAcquireContext( ref IntPtr hProv, string pszContainer, string pszProvider, CRYPT_PROVIDER_TYPE dwProvType, CRYPT_ACQUIRE_CONTEXT_FLAGS dwFlags ); /// <summary>Function from Crypto API.</summary> [DllImport( "crypt32.dll", SetLastError = true, CharSet = CharSet.Auto )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CryptStringToBinary( string sPEM, UInt32 sPEMLength, CRYPT_STRING_FLAGS dwFlags, [Out] byte[] pbBinary, ref UInt32 pcbBinary, out UInt32 pdwSkip, out UInt32 pdwFlags ); /// <summary>Function from Crypto API.</summary> [DllImport( "crypt32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CryptDecodeObjectEx( CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, UInt32 cbEncoded, CRYPT_DECODE_FLAGS dwFlags, IntPtr pDecodePara, ref byte[] pvStructInfo, ref UInt32 pcbStructInfo ); /// <summary>Function from Crypto API.</summary> [DllImport( "crypt32.dll", SetLastError = true )] [return: MarshalAs( UnmanagedType.Bool )] internal static extern bool CryptDecodeObject( CRYPT_ENCODING_FLAGS dwCertEncodingType, IntPtr lpszStructType, byte[] pbEncoded, UInt32 cbEncoded, CRYPT_DECODE_FLAGS flags, [In, Out] byte[] pvStructInfo, ref UInt32 cbStructInfo ); #endregion P/Invoke Functions } |
I am attaching a Visual Studio 2012 solution containing a sample usage for PEM and DER encoded files. Click here to download.
Solution containing sample keys:
Test
program output:
.NET RSACryptoServiceProvider PEM + DER Support,古老的榕树,5-wow.com
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。