如何在服务器端使用PHP生成的public密钥?

How to use the public key generated by using PHP on the server?

在服务器上,我有一个 PHP 脚本 (Laravel) 可以生成 RSA 密钥对,将它们闪存到会话中,并且 return base64 编码的 public 密钥,将在我的 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 并检索 public 密钥

    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 PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwg6U1EET7OSbLO7UUZh 7p8ODYY4kXUd5S1Z/qexG5IqpNflrdQbpVh+8KWNi83oidAUjWEb050Rl3AuY/E6 7hYlEdUvI9pevmBpjjU1GktzQsDsbva3THHSpTZXPlctnFnuz0b5hVu1nUETmbGF fSbslZet3pbKcK5KGnJpm6v6OQGpvgQjyNWF16HjUD4/x1rAL2aDNOZZED+FNJcC hNmdK1A8nECk1JoTTdiK7r0EXMWxdVjEaSkAsvi7ywKi7ZESWwS1JmRuIJ5ZPiRx Fvur1tgaiomEZ+9oDpk1+bwDenrERYgBxw2L6Rw0CwuyinhwYfIbWkNmy4cAiZVx KwIDAQAB -----END PUBLIC KEY-----

我尝试使用RSAParameters,但模数是原始字节数组(数据)还是从剥离字符串转换而来的数组(publicKey),使用RSACryptoServiceProvider的Encrypt方法加密的encryptedText在服务端无法被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 格式。这种不兼容导致了这个问题。

所以,你有什么解决办法吗?

我从这个 post 得到了一个解决方案:

在使用 RSA 参数 class.

时,我们需要注意很多事情
  1. 建议使用 public 密钥进行编码 base64_encode 在服务器上,以便它可以在 json 字符串中传递给客户端。我使用 phpseclib 而不是 openssl。 生成 PKCS#1 格式 public 密钥
  2. 在C#中,将base64编码的字符串解码为可读的public密钥 字符串,格式如下:
        -----BEGIN RSA PUBLIC KEY-----
        base64 encoded data
         -----END RSA PUBLIC KEY-----
  1. 在 C# 中,检索 public 关键数据并提取模数和 public 指数字节数组。在这一步,我们应该支付一个 特别注意 RSAParameters class 中的 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 编码的 public 密钥字符串

加密输入纯文本的函数
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',"[=13=]",'\'], ['/', '', '',""], $request->encrypted_text);
            $rsa->loadKey($privkey);
            $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
            $decrypt_text2 = $rsa->decrypt(base64_decode($received));
            $decrypt_text2 = str_replace("[=13=]",'',$decrypt_text2);

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

此外,如果我们只需要模数和指数而不是完整的 public 密钥,我们可以将模数和指数传递给客户端 (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

希望这对您有所帮助。