简体   繁体   中英

Sign a string with rsa private key in c# and verify it with openSSL

I am running the following code in order to sign a string in c# using an rsa private key. I extracted the private key from an X.509 certificate and converted it with openssl rsa command.

public static byte[] doSign(string text)
    {
        RSACryptoServiceProvider csp = CreateRsaProviderFromPrivateKey(File.ReadAllText(@"Security\private.pem"));

        SHA1Managed sha1 = new SHA1Managed();
        byte[] data = Encoding.ASCII.GetBytes(text);
        byte[] hash = sha1.ComputeHash(data);

        return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
    }

    private static RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey)
    {
        List<string> lines = privateKey.Split('\n').ToList();

        string key = string.Join("",
            (
                from s in lines
                where !string.IsNullOrEmpty(s) && s[0] != '-'
                select s
             ).ToList().ToArray()).Replace("\n", "").Replace("\r", "");


        var privateKeyBits = System.Convert.FromBase64String(key);

        var RSA = new RSACryptoServiceProvider();
        var RSAparams = new RSAParameters();

        using (BinaryReader binr = new BinaryReader(new MemoryStream(privateKeyBits)))
        {
            byte bt = 0;
            ushort twobytes = 0;
            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130)
                binr.ReadByte();
            else if (twobytes == 0x8230)
                binr.ReadInt16();
            else
                throw new Exception("Unexpected value read binr.ReadUInt16()");

            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102)
                throw new Exception("Unexpected version");

            bt = binr.ReadByte();
            if (bt != 0x00)
                throw new Exception("Unexpected value read binr.ReadByte()");

            RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.D = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.P = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
        }

        RSA.ImportParameters(RSAparams);
        return RSA;
    }

I am then converting the signed string to an hexadecimal string and send it to a php page:

private void toPhp(string timestamp)
    {
        string data = idMachine + numberOfSignal + timestamp;

        //string signature = new Sign().IngApiGetToken(data);

        byte[] signatureBytes = Sign.doSign(data);
        string signature = Sign.toHex(Encoding.UTF8.GetString(signatureBytes));

        using (HttpClient client = new HttpClient())
        {
            string uri = "http://localhost/GCS/";

            var formContent = new FormUrlEncodedContent(new[]
            {
                new KeyValuePair<string, string>("data", data),
                new KeyValuePair<string, string>("signature", signature)
            });

            var res = client.PostAsync(uri, formContent).Result.Content.ReadAsStringAsync().Result;

            //textBox1.Text += "Hex: " + signature;
            textBox1.Text += Environment.NewLine;
            textBox1.Text += "---- Sending request to PHP ----";
            textBox1.Text += Environment.NewLine;
            textBox1.Text += res;
        }
    }

The php page verifies the signed string using the certificate and tries to sign itself the original data:

    $data = $_POST["data"];
$signature = $_POST["signature"];
$bin = $_POST["bin"];

if(!isset($_POST['bin'])){
    $signature = hex2bin($signature);
}

$pub_key = openssl_pkey_get_public("file://cert.pem"); 



echo "Data: ";
echo $data;
echo "\r\nSignature: ";
echo $signature;
echo "\r\nSignature to hex: ";
echo strtoupper(bin2hex($signature));
echo "\r\nResult: ";
echo openssl_verify ( $data , $signature , $pub_key); 

//Re-sign
$res = openssl_get_privatekey("file://private.pem");
openssl_sign($data, $newSignature, $res);

$hex = strtoupper(bin2hex($newSignature));

echo "\r\nString signed hex: $newSignature";
echo "\r\nString signed hex: $hex";
echo "\r\nResult: ";
echo openssl_verify ( $data , $newSignature , $pub_key); 

echo "\r\nComparing signed: ";
echo strcmp ($signature,$newSignature);

Below you can see the result printed by the php echos. As you can see, the signed strings are apparently identical, but the hexadecimal values are different.

---- Sending request to PHP ---- Data: 22-530057097401597056984 Signature: B���p E]% ��.qנ�[��؋�� GX�%0�,f-�e"�+�Ӎ�lf�q�^��:����yN51�C�:��V�n�i���eECx������ Q�=H33���9�#��Aw�ݘ�sa=f=6A�~��u��i/ui6�[�Z�F�+DБH����N�K�-/v�=�I�HM7� |Ҧ��e/v�����}vxB:k��Z4s�E��$ߘ�rVU^^}�:�)i�������X��"�68�X�Ӝ2{{sH Signature to hex. Result, 0 String signed hex: B���p E]% ��:qנ�[��؋�� GX�%0�:f-�e"�+�Ӎ�lf�q�^��:����yN51�C�!��V�n�i���eECx������ Q�=H33���9�#��Aw�ݘ�sa=f=6A�~��u��i/ui6�[�Z�F�+DБH����N�K�-/v�=�I�HM7� |Ҧ��e/v�����}vxB,k��Z4s�E��$ߘ�rVU^^}�:�)i�������X��"�68�X�Ӝ2{{sH String signed hex: 4289AAE2700D45175D250C85ED9C2E0371D7A0D45BAEDDD88BA2F70D4758DC2530EC21662DA6106522E62BC2D38DAA6C668671975EF481BE1F8921108796ABBA794E3531C27F43BF2196DA56D96EB269B490B36545430578BDFA9CE2D91613850C0251D53D4833338FFDEF398D239FBD4177FCDD98A67302613D663D3641EB7EB611D8751ADAE6692F04756936B75B111B8D5AEB860546F92B44D0914888CB04F8EC4EA14BBC2D2F76AC3D84498D12484D37D30A7CD2A6C4ED652F76DAF11CD1F7A97D7678422C106B9B965A3473CC45819724DF98B77256555E5E7D87053ACD0B142969868ED5C0A3CDE65898D122188E3638E95897D39C327B7B1D13731348 Result: 1 Comparing signed: 1

Anyone can help about signing in C# in order to make the openssl_verify return 1? Thank you very much!

Turns out that there was a bug in converting the byte array into an hexadecimal string. Thank you Topaco for your comment!

In toPhp() method I changed the following line:

string signature = Sign.toHex(Encoding.UTF8.GetString(signatureBytes));

With this one:

string signature = Sign.ByteArrayToString(signatureBytes);

And here's the implementation, stolen from here :

public static string ByteArrayToString(byte[] ba)
    {
        StringBuilder hex = new StringBuilder(ba.Length * 2);
        foreach (byte b in ba)
            hex.AppendFormat("{0:x2}", b);
        return hex.ToString();
    }

I'm running just an online-compiler for C# so I can't test your C#-code completely. Maybe you should try to change your doSign method as follows:

original code:

public static byte[] doSign(string text)
{
    RSACryptoServiceProvider csp = CreateRsaProviderFromPrivateKey(File.ReadAllText(@"Security\private.pem"));
    SHA1Managed sha1 = new SHA1Managed();
    byte[] data = Encoding.ASCII.GetBytes(text);
    byte[] hash = sha1.ComputeHash(data);
    return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}

new code:

public static byte[] doSign(string text)
{
    RSACryptoServiceProvider csp = CreateRsaProviderFromPrivateKey(File.ReadAllText(@"Security\private.pem"));
    byte[] data = Encoding.ASCII.GetBytes(text);
    return csp.SignData(data, new SHA1CryptoServiceProvider());
}

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