如何使用通过服务器上的PHP生成的公钥?

时间:2019-11-18 01:10:23

标签: c# php laravel openssl rsa

在服务器上,我有一个PHP脚本(Laravel),可以生成RSA密钥对,将它们刷新到会话中,并返回base64编码的公共密钥,该密钥将在我的Windows中使用应用程序来加密密码,并通过调用 openssl_pkey_new 函数创建密钥:

// get RSA key pairs
public function generateRSAKeyPairTest(Request $request)
{
    $t = $this->microtime_float();

    $res = openssl_pkey_new();
    openssl_pkey_export($res, $privkeyraw);
    $d= openssl_pkey_get_details($res);
    $pubkeyraw = $d['key'];
    $pubkey = base64_encode($pubkeyraw);
    $privkey = base64_encode($privkeyraw);
    $request->session()->flash('privkey', $privkey);
    $request->session()->flash('pubkey', $pubkey);

    $tdiff = $this->microtime_float() - $t;
    $keypair = array('pubkey'  => $pubkey,'diff' => $tdiff);

    return response()->json($keypair);
}

// decode the encryted string
public function decryptRSATest(Request $request)
{
    if ($request->session()->has('privkey')) 
    {
        $privkey = $request->session()->get('privkey', 'default value');
        $resPriv = openssl_pkey_get_private(base64_decode($privkey));

        $encrypted_text_base64 = $request->encrypted_text;
        $outval = '-';
        $encrypted_text = base64_decode($encrypted_text_base64);
        openssl_private_decrypt($encrypted_text, $outval, $resPriv);
        return response()->json(
            [
                 'decoded_text_from_clit'=>$outval
            ]
        );
    }
    return response()->json(["error"=>"private key does not exist!"]);
}

然后在我的 Windows应用程序(C#)中,我从服务器获取json并检索公钥

    var data = Convert.FromBase64String(publicKeyStringBase64);
    var publicKeyRaw = Encoding.UTF8.GetString(data);
    var pkStr = publicKeyRaw.Replace("-----END PUBLIC KEY-----", "").Replace("-----BEGIN PUBLIC KEY-----", "");
    var publicKey = Encoding.UTF8.GetBytes(pkStr);

publicKeyRaw 如下(PKCS#8格式):

  

----- BEGIN公钥-----
  MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwg6U1EET7OSbLO7UUZh   7p8ODYY4kXUd5S1Z / qexG5IqpNflrdQbpVh + 8KWNi83oidAUjWEb050Rl3AuY / E6   7hYlEdUvI9pevmBpjjU1GktzQsDsbva3THHSpTZXPlctnFnuz0b5hVu1nUETmbGF   fSbslZet3pbKcK5KGnJpm6v6OQGpvgQjyNWF16HjUD4 / x1rAL2aDNOZZED + FNJcC   hNmdK1A8nECk1JoTTdiK7r0EXMWxdVjEaSkAsvi7ywKi7ZESWwS1JmRuIJ5ZPiRx   Fvur1tgaiomEZ + 9oDpk1 + bwDenrERYgBxw2L6Rw0CwuyinhwYfIbWkNmy4cAiZVx   科威达   ----- END公钥-----

我尝试使用RSAParameters,但是无论Modulus是原始字节数组(数据)还是从剥离字符串转换后的数组( publicKey ),加密文本都被加密使用RSACryptoServiceProvider的Encrypt方法通过服务器上的 openssl_private_decrypt 不能解密。

var rsaInfo = new RSAParameters()
{
    Exponent = new byte[] { 1, 0, 1 },
    Modulus = data, // publicKey 
};

var csp = new RSACryptoServiceProvider();
csp.ImportParameters(rsaInfo);
var password = "testing-passsword!";
var hash = csp.Encrypt(Encoding.UTF8.GetBytes(password), false);
var encryptedText = Convert.ToBase64String(hash);

我猜想RSACryptoServiceProvider不能正确加密密码,那么将这个密钥字符串(publicKeyRaw)与RSACryptoServiceProvider一起使用的正确方法是什么?

[更新]

在Google上进行了大量搜索工作后,问题就变得清晰了(与@James Reinstate Monica Polk提到的内容一样): 默认情况下,使用openssl_pkey_new()生成的密钥为 PKCS#8 格式,而RSACryptoServiceProvider仅接受 PKCS#1 v1.5 格式的密钥。这种不兼容导致了这个问题。

那么,您对此有任何解决方案吗?

1 个答案:

答案 0 :(得分:0)

我从这篇文章中找到了解决方案:Correctly create RSACryptoServiceProvider from public key

使用 RSAPrameters 类时,我们需要注意很多事情。

  1. 建议使用来对公钥进行编码 服务器上的 base64_encode ,以便可以使用json字符串将其传递给客户端。而且我使用phpseclib而不是openssl。 生成PKCS#1格式的公共密钥
  2. 在C#中,将base64编码的字符串解码为可读的公钥     字符串,其格式如下:
        -----BEGIN RSA PUBLIC KEY-----
        base64 encoded data
         -----END RSA PUBLIC KEY-----
  1. 在C#中,检索公钥数据并提取模数和 公共指数字节数组。在这一步中,我们应该支付 特别注意 RSAParameters 类中的 Modulus , 填写模量时,应跳过第一个字节, 是 0x00 。我实现了一个简单但不完整的解码器,如下所示:
public static RSAParameters GetRSAParametersFromPublicKey(string publicKeyString)
{
            // publicKeyString should have following format:

            // -----BEGIN RSA PUBLIC KEY---- -
            // base64 encoded data
            // -----END RSA PUBLIC KEY---- -

            var pubicKeyContentString = publicKeyString.Replace("-----BEGIN RSA PUBLIC KEY-----", "")
                .Replace("-----END RSA PUBLIC KEY-----", "")
                .Replace("\r", "")
                .Replace("\n", "")
                .Replace(@"\/", "/");
            var publicKeyArray = Convert.FromBase64String(pubicKeyContentString);

            var mask = 0x7F;
            var skipCount = 0;
            var rsaParameters = new RSAParameters();
            for (int i = 0; i < publicKeyArray.Length; i=skipCount)
            {
                var tag = publicKeyArray[i];
                var lengthLength = publicKeyArray[i + 1];
                var length = Convert.ToInt32(lengthLength);
                skipCount += 2;
                if (lengthLength > mask)
                {
                    var lengthBit = lengthLength & mask;
                    var lengthBytes = publicKeyArray.Skip(skipCount).Take(lengthBit).ToArray();
                    skipCount += lengthBit;
                    length = BitConverter.ToInt16(lengthBytes.Reverse().ToArray(),0);
                }
                if (tag == 0x02)
                {
                    // both modulus and public exponent start with 0x02: integer
                    // therefore, 0x02 is the only tag we are interested in
                    var valueBytes = publicKeyArray.Skip(skipCount).Take(length).ToArray();
                    if (valueBytes[0] == 0x00)
                    {
                        // a valid DER has a leading byte 0x00
                        rsaParameters.Modulus = valueBytes.Skip(1).ToArray();
                    }
                    else
                    {
                        rsaParameters.Exponent = valueBytes;
                    }
                    skipCount += length;
                }
            }
            return rsaParameters;
        }

这是使用输入的base64编码的公钥字符串对输入的plainText进行加密的函数

public static string Cryptography(string plainText, string publicKeyStringBase64)
{
    var publicKey = Convert.FromBase64String(publicKeyStringBase64);
    var publicKeyRaw = Encoding.UTF8.GetString(publicKey);
    var rsaInfo = GetRSAParametersFromPublicKey(publicKeyRaw);
    if (rsaInfo.Modulus != null && rsaInfo.Exponent != null)
    {
        var csp = new RSACryptoServiceProvider();
        csp.ImportParameters(rsaInfo);
        var hash = csp.Encrypt(Encoding.Unicode.GetBytes(plainText), false);
        var encryptedText = HttpUtility.UrlEncode(Convert.ToBase64String(hash));

        return encryptedText;
    }
    return "!!error!!";
}
  1. 在服务器上,我想使用phpseclib来生成PKCS#1密钥对并执行解码(laravel):
    use phpseclib\Crypt\RSA;

    public function decryptRSATest(Request $request)
    {
        if ($request->session()->has('privkey')) 
        {
            $privkeybase64 = $request->session()->get('privkey', 'default value');
            str_replace(['\/', '\n'], ['/', ''], $privkeybase64);
            $privkey = base64_decode($privkeybase64);

            $rsa = new RSA();
            $received = str_replace(['\/', '\n',"\0",'\\'], ['/', '', '',""], $request->encrypted_text);
            $rsa->loadKey($privkey);
            $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
            $decrypt_text2 = $rsa->decrypt(base64_decode($received));
            $decrypt_text2 = str_replace("\0",'',$decrypt_text2);

            return response()->json(
                [
                     'decoded_text_from_clit' => $decrypt_text2
                ]
            );
        }
        return response()->json(["error"=>"private key does not exist!"]);
    }

此外,如果我们只需要模数和指数而不是完整的公钥,则可以将模数和指数传递给客户端(base64_encoded):

            // PHP - Server end
            $modulus_base64 = base64_encode($rsa->modulus);
            $exponent_base64 = base64_encode($rsa->exponent);

在C#中,正确解码它们可能有些困难,这是一个代码段:

        // C# - Client end
        var modulus = Convert.FromBase64String(modulusStringBase64);
        var exponent = Convert.FromBase64String(exponentStringBase64);
        var modulusRaw = Encoding.UTF8.GetString(modulus);
        var exponentRaw = Encoding.UTF8.GetString(exponent);
        var bigIntegerModulus = BigInteger.Parse(modulusRaw);
        var bigIntegerExponent = BigInteger.Parse(exponentRaw);
        var exponentArray = bigIntegerExponent.ToByteArray();
        var modulusArray = bigIntegerModulus.ToByteArray();

和一些有用的在线工具:

base64 decoder

ASN 1 decoder

Json Viewer

希望这对您有帮助。

相关问题