简体   繁体   中英

Using code signing private key to encrypt in PHP, need to decrypt in C# using public key

I have written a method which uses my private key ( thawte states that the private key is not to be distributed. Signing happens using the private key, and embeds the public key into the file for verifying the signing).

Following is the PHP code I am using to encrypt (which I can decrypt using my public key in PHP successfully)

<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);

echo decode(sign("hello world"));

function sign($data) {
    $k = openssl_pkey_get_private(file_get_contents("key.pem"));
    $ret = "";
    $output = "";
    try{
        if( openssl_private_encrypt( $data, $output, $k ) ) {
            $ret = base64_encode($output);
        } else {
            $ret = "Failed";
        }
    } catch(Exception $e) {
        $ret = "Failed:" . $e->getMessage();
    }
    return $ret;
}


function decode($data) {
    $k = openssl_get_publickey(file_get_contents("public.cer"));
    $ret = "";
    $output = "";
    try{
        if( openssl_public_decrypt( base64_decode($data), $output, $k  ) ) {
            $ret = $output;
        } else {
            $ret = "Failed";
        }
    } catch(Exception $e) {
        $ret = "Failed:" . $e->getMessage();
    }
    return $ret;
}

I have also tried using the library available here phpseclib which claims to be a pure PHP method for encryption, using the following code to create the encrypted data :

include('Math/BigInteger.php');
include('Crypt/RSA.php');

echo sign2("Hello World");

function sign2($data) {
    $rsa = new Crypt_RSA();
    extract($rsa->createKey());
    $rsa->loadKey(file_get_contents("key.pem"));
    $ciphertext = $rsa->encrypt($data);
    return base64_encode($ciphertext);
}

Here is what I have so far in C#, but it is causing an exception on rsa.DecryptValue()

    public string Decrypt( string Key, string Data ) {
        X509Certificate2 cert=new X509Certificate2( Key, "", X509KeyStorageFlags.Exportable );
        RSACryptoServiceProvider rsa=cert.PublicKey.Key as RSACryptoServiceProvider;
        return GetString( rsa.DecryptValue( Convert.FromBase64String( Data ) ) );
    }

    static string GetString( byte[] bytes ) {
        char[] chars=new char[bytes.Length/sizeof( char )];
        System.Buffer.BlockCopy( bytes, 0, chars, 0, bytes.Length );
        return new string( chars );
    }

Here is a screenshot of the error I am receiving:

密钥不存在

So I attempted to try something a bit more complex using code obtained from Extracting Modules and Component(RSAParameter) from X509Certificate PublicKey in an attempt to fill the RSAParameter with needed modulus/etc to complete the decryption ...... it still fails.

using System;
using System.Threading;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

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


namespace TestCase
{
    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    public class TestCase
    {
        public static void Main(string[] certfilex)
        {
            string[] certfile = { @"D:\Server\www\127.0.0.1\htdocs\sign\public.cer" };
            string enc = ""; // Base64 encoded string is here, but removed for posting on SO
            if (certfile == null)
            {
                Console.WriteLine("Certificate filename is not valid");
                Environment.Exit(0);
            }

            try
            {
                BinaryReader br = new BinaryReader(File.OpenRead(certfile[0]));
                byte[] buf = new byte[br.BaseStream.Length];
                br.Read(buf, 0, buf.Length);
                X509Certificate cert = new X509Certificate(buf);

                RSAParameters p = X509PublicKeyParser.GetRSAPublicKeyParameters(cert);
                X509Certificate2 crt=new X509Certificate2( certfile[0], "", X509KeyStorageFlags.Exportable );
                using ( RSACryptoServiceProvider rsa=crt.PublicKey.Key as RSACryptoServiceProvider ) {
                    rsa.ImportParameters(p);
                    byte[] unb64 = Convert.FromBase64String(enc);
//                  Array.Reverse(unb64);
                    try {
                        byte[] results=rsa.Decrypt( unb64, false );
                        Console.WriteLine( GetString( results ) );
                    } catch ( CryptographicException ce ) {
                        Console.WriteLine(ce.Message);
                    }
                }

//                HexDump("Modulus:", p.Modulus);
//                HexDump("Exponent:", p.Exponent);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message + " \r\n " + e.StackTrace);
                Environment.Exit(0);
            }
            Console.ReadLine();
        }

        static byte[] GetBytes( string str ) {
            byte[] bytes=new byte[str.Length*sizeof( char )];
            System.Buffer.BlockCopy( str.ToCharArray(), 0, bytes, 0, bytes.Length );
            return bytes;
        }

        static string GetString( byte[] bytes ) {
            char[] chars=new char[bytes.Length/sizeof( char )];
            System.Buffer.BlockCopy( bytes, 0, chars, 0, bytes.Length );
            return new string( chars );
        }


        /// <summary>
        /// Converts and writes the bytes as hexadecimal values
        /// </summary>
        /// <param name="label">Dump header</param>
        /// <param name="ba">value to be printed</param>
        public static void HexDump(string label, byte[] ba)
        {
            const string SPC = " ";
            string tid = Thread.CurrentThread.Name;
            if (ba == null)
            {
                ba = new byte[0];
            }
            Console.WriteLine(label + " [" + tid + "], " + ba.Length);
            StringBuilder buf = new StringBuilder();
            string LINE_SEP = Environment.NewLine;
            for (int i = 0; i < ba.Length; i++)
            {
                if (i > 0 && (i % 16) == 0)
                {
                    buf.Append(LINE_SEP);
                }
                if (i % 8 == 0)
                {
                    buf.Append(SPC);
                }
                string str = Convert.ToString(ba[i], 16);
                if (str.Trim().Length == 1)
                {
                    str = "0" + str.Trim();
                }
                buf.Append(str + SPC);
            }
            if (ba.Length > 0)
            {
                Console.WriteLine(buf.ToString().ToUpper());
            }
        }

    }



    internal class IntegerContainer : AbstractAsn1Container { internal IntegerContainer( byte[] abyte, int i ) : base( abyte, i, 0x2 ) { } }
    internal class SequenceContainer : AbstractAsn1Container { internal SequenceContainer( byte[] abyte, int i ) : base( abyte, i, 0x30 ) { } }

    public class X509PublicKeyParser
    {
        public static RSAParameters GetRSAPublicKeyParameters(byte[] bytes) { return GetRSAPublicKeyParameters(bytes, 0); }

        public static RSAParameters GetRSAPublicKeyParameters(byte[] bytes, int i) {
            SequenceContainer seq = new SequenceContainer(bytes, i);
            IntegerContainer modContainer = new IntegerContainer(seq.Bytes, 0);
            IntegerContainer expContainer = new IntegerContainer(seq.Bytes, modContainer.Offset);
            return LoadKeyData(modContainer.Bytes, 0, modContainer.Bytes.Length, expContainer.Bytes, 0, expContainer.Bytes.Length);
        }

        public static RSAParameters GetRSAPublicKeyParameters(X509Certificate cert) { return GetRSAPublicKeyParameters(cert.GetPublicKey(), 0); }

        private static RSAParameters LoadKeyData(byte[] abyte0, int i, int j, byte[] abyte1, int k, int l) {
            byte[] modulus = null;
            byte[] publicExponent = null;
            for(; abyte0[i] == 0; i++)
                j--;

            modulus = new byte[j];
            Array.Copy(abyte0, i, modulus, 0, j);
            int i1 = modulus.Length * 8;
            int j1 = modulus[0] & 0xff;
            for(int k1 = j1 & 0x80; k1 == 0; k1 = j1 << 1 & 0xff)
                i1--;

            if(i1 < 256 || i1 > 2048)
                throw new X509ParserException("Invalid RSA modulus size.");
            for(; abyte1[k] == 0; k++)
                l--;

            publicExponent = new byte[l];
            Array.Copy(abyte1, k, publicExponent, 0, l);
            RSAParameters p = new RSAParameters();
            p.Modulus = modulus;
            p.Exponent = publicExponent; 
            return p;
        }
    }

    public class X509ParserException : SystemException {
        public X509ParserException() : base() { }
        public X509ParserException( string msg ) : base( msg ) { }
        public X509ParserException( string msg, Exception e ) : base( msg, e ) { }
    }

    /// <summary>
    /// Summary description for AbstractAsn1Container.
    /// </summary>
    internal abstract class AbstractAsn1Container {
        private int offset;
        private byte[] data;
        private byte tag;

        internal protected AbstractAsn1Container( byte[] abyte, int i, byte tag ) {
            this.tag=tag;
            if ( abyte[i]!=tag ) {
                throw new X509ParserException( "Invalid data. The tag byte is not valid" );
            }
            int length=DetermineLength( abyte, i+1 );
            int bytesInLengthField=DetermineLengthLen( abyte, i+1 );
            int start=i+bytesInLengthField+1;
            this.offset=start+length;
            data=new byte[length];
            Array.Copy( abyte, start, data, 0, length );
        }

        internal int Offset {
            get {
                return offset;
            }
        }

        internal byte[] Bytes {
            get {
                return this.data;
            }
        }

        internal protected virtual int DetermineLengthLen( byte[] abyte0, int i ) {
            int j=abyte0[i]&0xff;
            switch ( j ) {
                case 129:
                    return 2;

                case 130:
                    return 3;

                case 131:
                    return 4;

                case 132:
                    return 5;

                case 128:
                default:
                    return 1;
            }
        }

        internal protected virtual int DetermineLength( byte[] abyte0, int i ) {
            int j=abyte0[i]&0xff;
            switch ( j ) {
                case 128:
                    return DetermineIndefiniteLength( abyte0, i );

                case 129:
                    return abyte0[i+1]&0xff;

                case 130:
                    int k=( abyte0[i+1]&0xff )<<8;
                    k|=abyte0[i+2]&0xff;
                    return k;

                case 131:
                    int l=( abyte0[i+1]&0xff )<<16;
                    l|=( abyte0[i+2]&0xff )<<8;
                    l|=abyte0[i+3]&0xff;
                    return l;
            }
            return j;
        }

        internal protected virtual int DetermineIndefiniteLength( byte[] abyte0, int i ) {
            if ( ( abyte0[i-1]&0xff&0x20 )==0 )
                throw new X509ParserException( "Invalid indefinite length." );
            int j=0;
            int k;
            int l;
            for ( i++; abyte0[i]!=0&&abyte0[i+1]!=0; i+=1+k+l ) {
                j++;
                k=DetermineLengthLen( abyte0, i+1 );
                j+=k;
                l=DetermineLength( abyte0, i+1 );
                j+=l;
            }

            return j;
        }
    }
}

What I would like, is to do the equivalent to the above decode method, but in C#. I have seen someone spam their response across the internet (including on StackOverflow), however their claim of "it's not possible", is not logical or valid.

I am looking for a serious well thought answer to this. From what I have tested, loading the certificate/etc isn't that complex, it's more just finding the right combination of commands for the task.

Thanks in advance.

(Adding a note that I am not looking for use of 3rd party libraries. This is feasable using .NET framework only.)

What you're actually doing here is verifying and recovering a short signed message - this won't work on messages much larger than 200 bytes since it's using the RSA key pair directly.

EDIT : this may work. I didn't know about PublicKey.Key doing the parameter-shoveling work for you, so I was busy writing a screen full of byte-bashing nastiness. You don't need the bytes-to-string conversion code either. To my utter astonishment, the final result actually comes out shorter. :-)

public string Decrypt( string Key, string Data ) {
    var cert = new X509Certificate2( Key );
    var rsa = (RSACryptoServiceProvider) cert.PublicKey.Key;
    return Encoding.Default.GetString( rsa.Decrypt( Convert.FromBase64String( Data ), false ) );
}

As an aside, I highly recommend C-style casting instead of as in this case, since it throws InvalidCastException promptly at the operator if the cast fails. as -casts silently return null , causing the next line to throw a NullReferenceException instead.


I can offer a variation that uses the Bouncy Castle library, since it provides a generous API surface including some OpenSSL-like capabilities.

using System.IO;

using Org.BouncyCastle.Crypto.Encodings;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.X509;

internal class Example
{
    internal static string Decode(string data)
    {
        try
        {
            X509Certificate k;

            // read in the certificate
            using (var textReader = File.OpenText("public.cer"))
            {
                // I'm assuming here that the certificate is PEM-encoded.
                var pemReader = new PemReader(textReader);
                k = (X509Certificate)pemReader.ReadObject();
            }

            // un-base64 the input
            var dataBytes = Convert.FromBase64String(data);

            // what php openssl_public_decrypt does
            var cipher = new OaepEncoding(new RsaEngine());
            cipher.Init(false, k.GetPublicKey());
            var returnBytes = cipher.ProcessBlock(dataBytes, 0, dataBytes.Length);

            // What is the character-encoding of the bytes? UTF-8?
            return Encoding.UTF8.GetString(returnBytes);
        }
        catch (Exception ex)
        {
            return "Failed:" + ex.Message;
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM