安全通信 PHP (phpseclib) 和 C# (Unity 3D)

Secure communication PHP (phpseclib) and C# (Unity 3D)

我正在尝试在 PHP 服务器和 Unity 3D 游戏(在网络播放器中)之间建立安全的 RSA 连接。在过程结束时 $rsa->decrypt() return "false" :-(

服务器生成 RSA 密钥并将 public 密钥发送到 Unity:

$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);

$keys = $rsa->createKey(512);
extract($keys);
$rsa->loadKey($publickey);
$_SESSION["privatekey"] = $privatekey;

$this->payload->Modulus = base64_encode($rsa->modulus);
$this->payload->Exponent = base64_encode($rsa->publicExponent);

这会生成 JSON:

{"Modulus":"MTE5MjcyOTYyNjQzMTIzODQ1MTI4MjE2ODA3OTY2MDE5MDQwODQ1NTc0MDIzMDI0NDQ5MjAzMDY4NDgxNTkyNzk5MTc0MzYxMzI4MDA3Njk0MjI4NjAyMzAwODA4MDI5MzkwOTk2MjUyMTg5OTkwNDgwNzg3MDcwMjk4MjkxMjcxNjQ1NzMzNDg0MTcxNTc0MDM3ODM0NjE3ODE=","Exponent":"NjU1Mzc="}

统一编码器:

var N = JSON.Parse (generatedJSON); //im using SimpleJSON library
var publicKey = new RSAParameters ();
publicKey.Modulus = Convert.FromBase64String(N ["Modulus"].Value);
publicKey.Exponent = Convert.FromBase64String(N ["Exponent"].Value);

var csp = new RSACryptoServiceProvider(512);
csp.ImportParameters(publicKey);
var plainTextData = "Hello Wordl"; //here come AES key generator (not implemented yet)
var bytesPlainTextData = System.Text.Encoding.Unicode.GetBytes(plainTextData);
var bytesCypherText = csp.Encrypt(bytesPlainTextData, false);
var cypherText = Convert.ToBase64String(bytesCypherText);

统一回复:

Nqsno9tTXWr4UfwoXQcHwzwnusvdKWpVGoakSsVECh3lH/3bNaPKY9LzZ9iZIs8RI9e5EI+GvegnxrW5xoqnyrDHbF8AuWh9Hndnn0OS5SV/kiYeBT6Wn9pxwjq5MoixM3geushHpvGTDQV0NOLcsXTdv8tG0CvFZip31GpMp9C/OalxolpaUvk65YBJ0dJcyNiuD08PQJAupJXKnVgfLZ0i1GrjQ7guHO6OmEUKDyQcZ5Sf/6yJry3Mhv2R4ioR/jU+mL4tLKuix5+/XKmBjg==

和服务器解码器:

$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS1);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);

$rsa->loadKey($_SESSION["privatekey"]);
$message = $_POST["unityResponse"];
$ciphertext = base64_decode($message);

$this->payload->message = $rsa->decrypt($ciphertext);

Return "Decryption error in..." :(

我认为一般问题出在 C# 中...你能帮我吗?

已解决!

另一种方式 - XML 密钥格式

服务器代码:

$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_XML);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_XML);

$keys = $rsa->createKey(1024);
extract($keys);
$rsa->loadKey($publickey);
$_SESSION["privatekey"] = $privatekey;
$this->payload->publickey = $publickey;

生成 JSON:

{"publickey":"<RSAKeyValue>\r\n  <Modulus>sCL/O7uUl4T32nUxZhKj1svDG88k4jkGsh/7IiFX/kTbj3mXmtYPLn5xTyLxEt9FxA/aoVxBmh1k7wA7lvcu5z4Avw6+AA/j793iDEhGdfvmXmHxm05xkjYO+LZ449YGDst9DUDO8SDA948Rld+eA187d/nhVPPAmun7RbLXBrs=</Modulus>\r\n  <Exponent>AQAB</Exponent>\r\n</RSAKeyValue>"}

Unity 网络播放器代码:

var N = JSON.Parse (generatedJSON);
var csp = new RSACryptoServiceProvider(1024);
csp.FromXmlString (N ["publickey"]);
var plainTextData = "Hello from Web Player";
var bytesPlainTextData = System.Text.Encoding.Unicode.GetBytes(plainTextData);
var bytesCypherText = csp.Encrypt(bytesPlainTextData, false);
var cypherText = Convert.ToBase64String(bytesCypherText);

最后服务器解密:

$rsa = new Crypt_RSA();
$rsa->setEncryptionMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_XML);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_XML);

$rsa->loadKey($_SESSION["privatekey"]);
$cypherText= $_POST["cypherText"];
$bytesCipherText = base64_decode($message);
$this->payload->encrypted = $rsa->decrypt($bytesCipherText);

你得到 JSON:

{"message":"Hello from Web Player"}

感谢完美的 phpsec 库!这是史诗!

我在这里分享我的示例 Unity 项目。希望它对某些人有用;)

GitHub: unity-php-secure-communication

<?php
<?php
/**
 * test with curl:
 *
 *  // Generate keypair : return XML public key
 *  curl -c cookies.txt -d "keygen=1" http://exemple.com//encrypt.php
 *
 *  // Test encrypt/decrypt : return encrypted and decrypted `my text to encode`
 *  curl -b cookies.txt -d "test=my text to encode" http://exemple.com//encrypt.php
 *
 *  // Test encrypt : return encrypted
 *  curl -b cookie.txt -d "encrypt=my text to encode" http://exemple.com//encrypt.php > encrypted.txt; cat encrypted.txt
 *
 *  // Test decrypt : return decrypted `my text to encode`
 *  curl -b cookie.txt -d "decrypt=`cat encrypted.txt`" http://exemple.com//encrypt.php
 */

include 'vendor/autoload.php';
use phpseclib\Crypt\RSA;

function generateKeyPair(){
    if (!isset($_SESSION['publickey'])){
        $rsa = new RSA();
        $rsa->setPrivateKeyFormat(RSA::PRIVATE_FORMAT_XML);
        $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_XML);
        $keys = $rsa->createKey();
        $_SESSION['privatekey'] = $keys['privatekey'];
        $_SESSION['publickey'] = $keys['publickey'];
    }
    return $_SESSION['publickey'];
}

function encrypt($cleartext){
    $rsa = new RSA();
    $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
    $rsa->setPublicKeyFormat(RSA::PUBLIC_FORMAT_XML);
    $rsa->loadKey($_SESSION['publickey']);
    $bytesCipherText = $rsa->encrypt($cleartext);
    return rawurlencode(base64_encode($bytesCipherText));
}

function decrypt($encrypted){
    $rsa = new RSA();
    $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1);
    $rsa->setPrivateKeyFormat(RSA::PRIVATE_FORMAT_XML);
    $rsa->loadKey($_SESSION['privatekey']);
    $bytesCipherText = base64_decode(rawurldecode($encrypted));
    return $rsa->decrypt($bytesCipherText);
}

if (isset($_POST['session_id'])) {
    session_id($_POST['session_id']);
}
session_start();

if (isset($_POST['keygen'])) {
    echo generateKeyPair();
    exit();
}

if (isset($_POST['encrypt'])) {
    echo encrypt($_POST['encrypt']);
    exit();
}

if (isset($_POST['decrypt'])) {
    echo decrypt($_POST['decrypt']);
    exit();
}

if (isset($_POST['test'])) {
    generateKeyPair();
    $ciphertext = encrypt($_POST['test']);
    echo "encrypted: $ciphertext\n\n";
    $clearText = decrypt($ciphertext);
    echo "decrypted: $clearText\n";
    exit();
}
using System;
using System.Collections;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
using UnityEngine.Analytics;
using UnityEngine.Networking;
using UnityEngine.UI;


public class EncryptController : MonoBehaviour
{
    public string ScriptUrl = "http://example.com/encrypt.php";

    public InputField ClearTextInputField;
    public InputField EncryptedTextInputField;
    public InputField ClearResponseInputField;

    public Text SessionIdText;
    public Text PublicKeyText;

    public void OnKeygenButtonClick()
    {
        StartCoroutine(GenerateRsaKeyPair());
    }

    public void OnEncryptButtonClick()
    {
        if (PublicKeyText.text.Length < 100)
        {
            Debug.LogError("You must generate RSA key pair before");
            return;
        }
        var csp = new RSACryptoServiceProvider(1024);
        csp.FromXmlString(PublicKeyText.text);
        var plainTextData = ClearTextInputField.text;
        var bytesPlainTextData = Encoding.Unicode.GetBytes(plainTextData);
        var bytesCypherText = csp.Encrypt(bytesPlainTextData, false);
        var cypherText = Convert.ToBase64String(bytesCypherText);
        EncryptedTextInputField.text = cypherText;
    }

    public void OnDecryptButtonClick()
    {
        if (PublicKeyText.text.Length < 100)
        {
            Debug.LogError("You must generate RSA key pair before");
            return;
        }
        StartCoroutine(SendEncryptedText(EncryptedTextInputField.text));
    }

    IEnumerator GenerateRsaKeyPair()
    {
        var form = new WWWForm();
        form.AddField("keygen", 1);
        form.AddField("session_id", AnalyticsSessionInfo.sessionId.ToString());
        SessionIdText.text = "sessionId=" + AnalyticsSessionInfo.sessionId.ToString();

        using (var www = UnityWebRequest.Post(ScriptUrl, form))
        {
            yield return www.SendWebRequest();

            if (www.isNetworkError)
            {
                Debug.LogError(www.error);
            }
            else
            {
                LogHeaders(www);

                Debug.Log(www.downloadHandler.text);
                PublicKeyText.text = www.downloadHandler.text;
            }
        }
    }

    IEnumerator SendEncryptedText(string text)
    {
        var form = new WWWForm();
        form.AddField("decrypt", text);
        form.AddField("session_id", AnalyticsSessionInfo.sessionId.ToString());
        SessionIdText.text = "sessionId=" + AnalyticsSessionInfo.sessionId.ToString();

        using (var www = UnityWebRequest.Post(ScriptUrl, form))
        {
            //www.SetRequestHeader("cookie", SessionIdText.text);
            yield return www.SendWebRequest();

            if (www.isNetworkError)
            {
                Debug.LogError(www.error);
            }
            else
            {
                LogHeaders(www);

                // Print Body
                Debug.Log(www.downloadHandler.text);
                ClearResponseInputField.text = www.downloadHandler.text;
            }
        }
    }

    private void LogHeaders(UnityWebRequest www)
    {
        var sb = new StringBuilder();
        foreach (var dict in www.GetResponseHeaders())
        {
            sb.Append(dict.Key).Append(": \t[").Append(dict.Value).Append("]\n");

            if (dict.Key == "Set-Cookie")
            {
                SessionIdText.text = dict.Value + " (from response headers)";
            }
        }
        Debug.Log(sb.ToString());
    }
}