【问题标题】:Using code signing private key to encrypt in PHP, need to decrypt in C# using public key使用代码签名私钥在 PHP 中加密,需要在 C# 中使用公钥解密
【发布时间】:2015-07-24 02:20:56
【问题描述】:

我编写了一个使用我的私钥的方法(thawte states 表示不会分发私钥。使用私钥进行签名,并将公钥嵌入文件中以验证签名)。

以下是我用来加密的 PHP 代码(我可以使用我在 PHP 中的公钥成功解密)

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

我也尝试过使用这里提供的库phpseclib,它声称是一种纯 PHP 加密方法,使用以下代码创建加密数据:

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

这是我目前在 C# 中的内容,但它导致 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 );
    }

这是我收到的错误的屏幕截图:

所以我尝试使用从Extracting Modules and Component(RSAParameter) from X509Certificate PublicKey 获得的代码尝试一些更复杂的东西,试图用所需的模数/等填充 RSAParameter 以完成解密......它仍然失败。

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

我想要做的是与上面的解码方法等效,但是在 C# 中。我已经看到有人在互联网上(包括在 StackOverflow 上)向他们的回复发送垃圾邮件,但是他们声称“不可能”是不合逻辑或无效的。

我正在寻找一个经过深思熟虑的严肃答案。根据我的测试,加载证书/等并不复杂,它只是为任务找到正确的命令组合。

提前致谢。

(添加说明我不是在寻找使用 3rd 方库。这仅使用 .NET 框架是可行的。)

【问题讨论】:

  • phpseclib 默认使用 OAEP 模式进行加密。它比 PKCS1 模式的替代方案更安全,但也不太常用。要使用 PKCS1 模式,请在 $rsa-&gt;encrypt() 之前执行 $rsa-&gt;setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);

标签: c# php encryption certificate


【解决方案1】:

您在此处实际执行的是验证和恢复短签名消息 - 这不适用于大于 200 字节的消息,因为它直接使用 RSA 密钥对。

编辑:这可能有效。我不知道PublicKey.Key 为你做参数铲除工作,所以我正忙着写一个充满字节抨击肮脏的屏幕。您也不需要字节到字符串的转换代码。令我大吃一惊的是,最终结果实际上更短。 :-)

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

顺便说一句,在这种情况下,我强烈建议使用 C 风格的转换而不是 as,因为如果转换失败,它会立即向操作员抛出 InvalidCastExceptionas-casts 默默地返回 null,导致下一行抛出 NullReferenceException


我可以提供一个使用 Bouncy Castle 库的变体,因为它提供了丰富的 API 表面,包括一些类似 OpenSSL 的功能。

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

【讨论】:

  • 可以在没有充气城堡的情况下这样做吗?
  • 我更新了我的帖子以包含我迄今为止在 C# 中部分工作的内容。证书加载正常,但我认为翻译中丢失了一些东西。在加载 x509 证书后检查 rsa 时,所有证书详细信息似乎都已到位。
  • @SanuelJackson,因为System.Security.Cryptography 没有公开展示一种从证书中获取密钥的方法,您可以插入RSAParameters,因此您最终会得到一堆讨厌的胶水代码解包二进制密钥子结构——或者不解包,如果你能找到正确的属性组合......
  • 如果需要的话,我可以接受一堆讨厌的胶水代码。我确实尝试过 >> stackoverflow.com/questions/7847743/… glue 不正确。
  • 我试图为那个胶球写一个更干净的版本,直到你用cert.PublicKey.Key 让我吃惊:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-06-30
  • 2021-02-17
  • 2020-02-28
  • 2017-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多