Securing Network Data Tutorial for Android
- Getting Started
- Understanding HTTPS
- Using Perfect Forward Secrecy
- Enforcing TLS With Network Security Configuration
- Understanding Certificate and Public Key Pinning
- Implementing Certificate Pinning
- Implementing TrustKit
- Using Certificate Transparency
- Stopping Information Leaks With OCSP Stapling
- Understanding Authentication
- Authenticating With Public-Key Cryptography
- Verifying Integrity With Elliptic-Curve Cryptography
- Verifying a Signature
- Signing a Request
- Additional Security Considerations
- Where to Go From Here?
Signing a Request
Another common scenario occurs when you connect to a server with a back-end API. Often, you'll need to register by sending your public key before you can access a specific endpoint, such as /send_message.
You can retrieve a
PublicKey's bytes by calling
publicKey.encoded. The app then needs to sign its request to the /send_message endpoint to use it successfully.
When signing a request, it's common to take selected parts of the request, such as HTTP Headers, GET or POST parameters, and the URL and join them into a string. You use that string to create the signature.
On the back end, the server repeats the process of joining the strings and creating a signature. If the signatures match, it proves that the user must have possession of the private key. No one can impersonate the user because they don't have that private key.
Since specific parameters of the request are part of the string, it also guarantees the integrity of the request. It prevents attackers from altering the request parameters. For example, a bank wouldn't be happy if attackers could alter the destination account number for a money transfer, or alter the mailing address to receive the victim's credit statements in the mail.
For your next step in making the app more secure, you'll create a simple signature for the pets' request.
Back in PetRequester.kt, add the following code to
retrievePets(), just under the line that declares
val bytesToSign = connection.url.toString().toByteArray(Charsets.UTF_8) // 1
val signedData = authenticator.sign(bytesToSign) // 2
val requestSignature = Base64.encodeToString(signedData, Base64.DEFAULT) // 3
Log.d("PetRequester", "signature for request : $requestSignature")
Here's what this code does:
- You take the request string and turn it into a
- The app signs the bytes using the private key and returns the signature bytes.
- You turn the signature bytes into a base 64 string so that you can easily send it over the network.
Now, add the following lines to verify that the signature works:
val signingSuccess = authenticator.verify(signedData, bytesToSign)
Log.d("PetRequester", "success : $signingSuccess")
Build and run to see the result in the Debug tab.
Now, you'll alter the request data to see what happens. Add the following code right before calling
bytesToSign[bytesToSign.size - 1] = 0
Build and run. This time,
false in the Debug tab.
Congratulations! You just secured the data with a signature.
Additional Security Considerations
You've been verifying the integrity of the data, but that's not a replacement for regular data validation checks such as type and bounds checking. For example, if your method expects a string of 128 characters or less, you should still check for this.
You should also be aware of a few other standards:
- RSA is a popular and accepted standard. Its key sizes must be much larger, such as 4096 bits, and key generation is slower. You might use it if the rest of your team is already familiar with or using this standard.
- HMAC is another popular solution that, instead of using public-key cryptography, relies on a single, shared key. You must exchange the secret key securely. Developers use HMAC when speed considerations are very important.
Where to Go From Here?
You've just secured an app for dealing with sensitive medical data. Download the final project using the Download Materials button at the top or bottom of this tutorial.
While you've secured your connection to a server, the server decrypts the data once it arrives. Sometimes it's a requirement for a company to be able to see this information, but there is a recent trend towards end-to-end encryption.
A good example of end-to-end encryption is a chat app where only the sender and receiver have the keys to decrypt each others' messages. The chat service has no way of knowing what the content is. This makes it a great way to avoid liability for a server-side data breach or compromise.
To learn more about implementing this approach, a good place to start is the open-source Signal App GitHub repo.
In this tutorial, you've been busy securing the network data in transit. Next, you should also protect the stored data and take the network cache into consideration.
Google has a network security testing tool to help you spot cleartext traffic or other connection vulnerabilities in your app. Visit nogotofail for more info.
For additional security tools, check out the SafetyNet API. It includes a safe browsing, integrity and reCAPTCHA API to protect your app from spammers, phishing URLs and other malicious traffic.
Finally, to check out Pom The Pomeranian, find him on Instagram. :]
If you have any questions about this tutorial, please join the discussion below!