We're building a game for Android, which needs access to web services - so we wrote a RESTful API in PHP that runs on our own server. What the API offers is: creating user, logging in, downloading games, retrieving game list, submitting score... etc. Now I'm thinking, if some experienced user gets the URL format of the API - s/he will be able to trash the system in many ways:
- Create a script & run it to create automatic users - I think I can prevent it by CAPTCHA or someting like that. But again, captcha will annoy game players.
- Malicious user logs in using his browser, downloads game & then submits score as he wish - all via calling the API by simply typing it from his browser. I assume malicious user somehow knows API urls to call - by sniffing when the application was making HTTP requests.
- I need to ensure that requests are made only from Android device that installed the game. (The game will be free)
Now How do I prevent such abuses?
I think you will never be able to hide the urls being called by the application
(if I am running a root-ed android phone, I should be able to spy on all network traffic)
But your real problem is that you need to authenticate your api in some way.
One way would be to implement OAUTH, but maybe this'd be overkill.
If you want a simple mechanism, how about this;
- create a secret key
- build the api request (eg. https://my.example.com/users/23?fields=name,email)
- hash this request path + plus your secret key (eg. md5(url+secret_key) == "a3c2fe167")
- add this hash to your request (now it is https://.....?fields=name,email&hash=a3c2fe167)
- on the api end, do the same conversion (remove the hash param)
- check the md5 of the url and the secret key
As long as the secret remains secret, no one can forge your requests.
Example (in pseudo-code):
Android side:
SECRET_KEY = "abc123"
def call_api_with_secret(url, params)
# create the hash to sign the request
hash = MD5.hash(SECRET_KEY, url, params)
# call the api with the added hash
call_api(url+"&hash=#{hash}", params)
end
Server side:
SECRET_KEY = "abc123"
def receive_from_api(url, params)
# retrieve the hash
url_without_hash, received_hash = retrieve_and_remove_hash(url)
# check the hash
expected_hash = MD5.hash(SECRET_KEY, url_without_hash, params)
if (expected_hash != received_hash)
raise our exception!
end
# now do the usual stuff
end
Solutions that others have presented here are called security through obscurity. Basically they are trying to obscure the protocol and hide the implementation. This might work until someone capable enough disassembles the app and reverse-engineers the protocol. Hackers are very capable at that.
The question is if your app is worth cracking? Schemes like iTunes, DVD or Sony PS3 network were obviously worth the effort. The obscurity approach might work if no one capable of cracking cares. Just don't fool yourself that it is not doeable.
Since you can not trust the device or your app, you must trust the user. In order to trust the user, you need user identification and authorization system. Basically a login to your app. Instead rolling you own indentification system (login with confirmation emails, etc..), use a 3rd party system: OpenID (google accounts) or OAuth (facebook, twitter). In case of facebook use the server-side auth scheme.
What I'd do:
- Allow users to freely play the game until they want to "save" the results on server.
- Before saving their results have them login via above mentioned method.
- Use HTTPS to send the data to your server. Buy a ssl certificate from trusted CA, so you don't have to deal with self-signed certs.
You mentioned users faking the high scores. This could still happen if your users are authenticated. When the game is uploading the high scores you may want to have it also upload a proof of the score. For example Score 20100 from 103 bugs squished, 1200 miles flown, level 3 reached, and 2 cherries were eaten. This is by no means perfect but would cover the low hanging fruit.
The first you should do is have authenticated users. Userid/password/session token etc., see if you can find some already existing frameworks. Once you have user authentication make sure you can do it securely with TLS or similar.
As far as I know there is no way your server can be certain that the request is coming from your application (it's all just bits in packets) but you can at least make it hard for someone to be malicious.
- Build a secret into your application (as suggested by other responses, key, hash salt etc.)
- Generate a unique ID on the first execution of the application after installation and track that along with the logged in user. Details on this and the device's unique ID (why not to use it) can be found on the android blog
- Some ideas discussed in this post How to ensure/determine that a post is coming from an specific application running on an iPhone/iTouch?
- Check User Agent
If you really want to secure the connection then you'll have to use public key cryptography, e.g. RSA. The device will encrypt the log in information using the public key and in the server end you will have to decrypt using the private key. After login the server will send a token/encryption key (the response will be an encrypted JSON or something) and the device will store that. From then as long as the session is not expired the device will send all the information encrypted using that token. For this requests you should not use RSA cause that will take more time. You can use AES256 (which is a popular private key encryption) with that encryption key received from server to encrypt your requests.
For sake of simplicity you can drop RSA altogether (If you are not sending payment information) and do everything using AES256 with a private key. The steps should be -
- Encrypt every outgoing request with a private key.
- Convert the encrypted string to a base 64 string.
- URL encode the base 64 encoded string.
- Send it over.
On the server end
- Do base 64 decode
- Decrypt using the private key.
Your request should carry a signature (e.g. the encryption key appended as a salt) so that it becomes possible to identify it after decrypting. If the signature is not present simply discard the request.
For sending responses do the same.
Android SDK should have methods for Encrypting with AES256 and Base 64 encoding.
Follow these guidelines from the Android team to secure your backend, by using Oauth tokens provided through Google's APIs.