2

I'm working with an API that specifies that certain text must be passed encoded with "RSA/ECB/PKCS1Padding". The public key was supplied in .PEM format; I think I've managed to convert it to XML format successfully, and I'm now using this C# code:

    RSACryptoServiceProvider rsa1 = new RSACryptoServiceProvider();
    rsa1.FromXmlString(testpublickey);

    System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
    byte[] textBytes = encoding.GetBytes(plainstring);
    byte[] encryptedOutput = rsa1.Encrypt(textBytes, false);
    string outputB64 = Convert.ToBase64String(encryptedOutput);
    Console.WriteLine(outputB64);
    return outputB64;

However, the returned string doesn't match what's expected - the API gives the following OpenSSL command as an example:

openssl rsautl -encrypt -in PIN.txt -inkey public_key_for_pin.pem -pubin | openssl base64 > PIN.base64

The output of OpenSSL doesn't match the output of my code. Can anyone see anything wrong with my code; do I need to specify the specific padding, or is it likely I've mangled the public key file in the translation from PEM to XML?

2
  • You're not setting the padding at all. Do you know the default? Commented Aug 23, 2011 at 21:02
  • @Henk Holterman: The padding is specified by the false argument in the rsa1.Encrypt() call. false gets you PKCS #1 block type 2 padding. Commented Aug 23, 2011 at 23:36

3 Answers 3

3

I do this to encrypt data with public key.

First load the PEM data into X509Certificate2 class, it has Import methods that you can use.

Then I use the below code:

using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography;

public static string EncryptString(string clearText, X509Certificate2 cert)
{
    try
    {
        byte[] encodedCypher = EncryptData(Encoding.UTF8.GetBytes(clearText), cert);
        string cipherText = Convert.ToBase64String(encodedCypher);

        return cipherText;
    }
    catch (Exception ex)
    {
        throw new EncryptionException("Could not Encrypt String. See InnerException for details.", ex);
    }
}

private static byte[] EncryptData(byte[] clearText, X509Certificate2 cert)
{
    ContentInfo payloadInfo = new ContentInfo(clearText);
    EnvelopedCms payloadEnvelope = new EnvelopedCms(payloadInfo);
    CmsRecipient certHandle = new CmsRecipient(cert);
    payloadEnvelope.Encrypt(certHandle);
    return payloadEnvelope.Encode();
}
Sign up to request clarification or add additional context in comments.

8 Comments

Thanks - that looks exactly what I need. However, I'm now having trouble getting the PEM file imported. The PEM file only has a public key - example: -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVIeL1dgxnKDy4IUmdHbIvY3Oz TlgnU+cfl9hlQgWFdFb2XwYvdj57ApeTbN8Gwaf6g/80+8XLOIWM+Zrl1n8ebooE TlgnU+cfl9hlQgWFdFb2XwYvdj57ApeTbN8Gwaf6g/80+8XLOIWM+Zrl1n8ebooE -----END PUBLIC KEY----- (not the real key). I can't get the .Import() method to accept that, any idea how to read a PEM file into a X509Certificate2 without it having a private key?
If you have a .PEM file on the file system, you can Import it by file name I think .cer is the extension that you can use (try to change the extension if it's not .cer). You can also use a raw byte array with the Import method. You use the overload of the Import method that doesn't require password and it will load the X509Certificate2 class with public key only.
I'm getting a "Cannot find the requested object" error when I try to import the file. My code looks like: X509Certificate2 cert = new x509Certificate2(); cert.Import("c:\\test\\test.cer"). It's definitely finding the file, and I've tried changing it to a raw byte array, but I'm still getting the same error :(
What happens when you open the test.cer file by double clicking it in windows explorer?
Also found this link.
|
0

The output is not supposed to match. For security reason RSA public key encryption using PKCS#1 block type 2 inserts random padding.

Comments

0

After spending some time mucking about with public key encryption, I thought I'd contribute to this old thread so someone might find it useful in the future.

Initializing an instance of X509Certificate2 object with the file containing only the public.key does not work. It expects a certificate, hence the "Cannot find the requested object" error.

Here's what has to be done in order to encrypt something using only the public.key file.

using System;
using System.IO;
using System.IO.Compression;
using System.Security.Cryptography;
using System.Text;
namespace Program
{
    class Program
    {
        static void Main( string[] args ) {

            // Load client's public key file.
            //
            RSAPublicKey = File.ReadAllText( @"public.key" );

            // Decode it
            //
            byte[] PEMPublicKey = DecodeOpenSSLPublicKey( RSAPublicKey );

            if( PEMPublicKey == null ){
                throw new Exception( "Could not decode RSA public key." );
            }

            //Create a new instance of RSACryptoServiceProvider with appropriate public key
            //
            RSACryptoServiceProvider RSA = DecodeX509PublicKey( PEMPublicKey );


            // Should you want to save your XMLPublicKey for future use of RSA.FromXmlString( XMLPublicKey );
            //
            // String XMLPublicKey = RSA.ToXmlString( false );


            byte[] cypher = RSA.Encrypt( Encoding.ASCII.GetBytes( "This is a test" ), false );
            File.WriteAllBytes( @"cypher.txt", cypher );

            return;

        }

        // The following code has been obtained from:
        // http://csslab.s3.amazonaws.com/csslabs/Siva/opensslkey.cs
        // All credits go to the unknown author
        //
        // --------   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 );


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

        }

        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[] Combine( byte[] first, byte[] second ) {
            byte[] ret = new byte[ first.Length + second.Length ];
            Buffer.BlockCopy( first, 0, ret, 0, first.Length );
            Buffer.BlockCopy( second, 0, ret, first.Length, second.Length );
            return ret;
        }

    }
}

You can test your result using openssl like so: openssl decrypt

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.