Search code examples
c#phprestunity-game-engineunity3d-mirror

C#/PHP How to build API that cannot be abused


I am making Unity multiplayer game with Mirror networking system. I want to store players data, like inventory, current location etc. in database (MySQL).

I have HTTPS-secured server, Unity client and this code for:

a) generating signature for request (C#)

    private const string SECRET_KEY_ALPHA = "alpha";
    private const string SECRET_KEY_BETA = "beta";

    private List<string> requestData;
    private string signature;

    public SignatureGenerator(List<string> requestData)
    {
        this.requestData = requestData;
    }

    public string GenerateSign()
    {
        this.signature = "";

        string bodyString = "";
        foreach(string dataPart in this.requestData)
        {
            bodyString += (dataPart + ".");
        }

        bodyString = EncryptMD5(bodyString);
        bodyString += SECRET_KEY_ALPHA;
        bodyString = EncryptSHA1(bodyString);

        this.signature = EncryptHmac(bodyString);

        return this.signature;
    }

    private String EncryptHmac(string combined)
    {
        Encoding ascii = Encoding.ASCII;
        HMACSHA256 hmac = new HMACSHA256(ascii.GetBytes(SECRET_KEY_BETA));
        return Convert.ToBase64String(hmac.ComputeHash(ascii.GetBytes(combined)));
    }

    private String EncryptMD5(string data)
    {
        using(MD5 md5 = MD5.Create())
        {
            byte[] dataBytes = Encoding.UTF8.GetBytes(data);
            byte[] md5Bytes = md5.ComputeHash(dataBytes);
            return BitConverter.ToString(md5Bytes).Replace("-", String.Empty);
        }
    }

    private String EncryptSHA1(string data)
    {
        using (SHA1Managed sha1 = new SHA1Managed())
        {
            var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(data));
            var sb = new StringBuilder(hash.Length * 2);

            foreach (byte b in hash)
            {
                sb.Append(b.ToString("X2"));
            }

            return sb.ToString();
        }
    }

b) verifying signature on server

define('SECRET_KEY_ALPHA', 'alpha');
define('SECRET_KEY_BETA', 'beta');

class SignatureVerifier
{
    private $bodyString;
    private $providedSignature;
    private $result;

    /**
     * SignatureVerifier constructor.
     * @param $bodyString
     * @param $providedSignature
     */
    public function __construct($bodyString, $providedSignature)
    {
        $this->bodyString = $bodyString;
        $this->providedSignature = $providedSignature;
    }

    public static function generateBodyString($dataArray)
    {
        $result = '';
        foreach ($dataArray as $data)
        {
            $result .= ($data . '.');
        }
        return $result;
    }

    public function verify()
    {
        $generatedSign = sha1(md5($this->bodyString), SECRET_KEY_ALPHA);
        $generatedHmac = hash_hmac('sha256', $generatedSign, SECRET_KEY_BETA);

        $this->result = ($generatedHmac == $this->providedSignature);
    }

    public function success()
    {
        return $this->result;
    }
}

But what is not fine for me with this? I have body and signature, so what do I want more?

Mine problem is that someone can catch this request (POST's & signs), and start spamming with those request to get some items.

Things I tried:

  • Add timestamp to signature

Result: Request will fail, because I will have to send & receive requests faster than second.

  • Add token to request, that is one-time usage (after use new is generated)

Result: If someone will decide to sell their item on market (website) - token will change, and whole system will get corrupt because game will have used, not working, token.

Summary: What I expect?

I expect secure API usages for client (Unity, C# game) and server (Apache, PHP), that cannot be abused (spamming with caught requests) and are secure so I can use it for inventory management in-game, logging in from game etc.

I hope I provided you all necessary data to solve mine problem. If you need something more - comments.

Thanks for the help in advance.


Solution

  • I have find out what can be helpful. I regret that I haven't thought about this.

    Expiration date (timestamp)!

    Include to your request AND signature timestamp, that will define expiration date of request. Then in PHP script, verify signature and timestamp (use same timezone in both services). If current timestamp is greater than provided one in request - reject request.

    Define how long should be exp. date. I recommend making some test requests to know how long game contacts API. Then add small amount on time to it (just in case longer request) and enjoy your more secured API.