Hide chapters

Real-World Android by Tutorials

Second Edition · Android 12 · Kotlin 1.6+ · Android Studio Chipmunk

Section I: Developing Real World Apps

Section 1: 7 chapters
Show chapters Hide chapters

17. Securing Data in Transit
Written by Antonio Roa-Valverde

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Network security is an integral part of development. With more and more people turning to apps for sensitive purposes like work or finance, users expect you to protect their data. Almost every app communicates over a network. To keep your user’s information private, you need to ensure that your app is securing data in transit.

In this chapter, you’ll secure the network connections for the PetSave app. During the process, you’ll learn the following best practices:

  • Using HTTPS for network calls.
  • Trusting a connection with certificate pinning.
  • Verifying the integrity of transmitted data.

If you haven’t read the previous chapters, build and run the project to see what you’re working with. Browse through the selection of pets and try tapping the report tab, which lets you send anonymous concerns:

Figure 17.1 — Report Session
Figure 17.1 — Report Session

In the previous chapter, you secured that data at rest. Now, your job is to ensure the data is secure when it leaves the app.

Understanding HTTPS

URLs that start with http:// transmit unprotected data that anyone can view — and many popular tools are available to monitor that data. Some examples are:

Because pets tend to be fussy about their privacy, the requests in this app use HTTPS. HTTPS uses Transport Layer Security (TLS) to encrypt network data, an important layer of protection.

All you need to do to ensure a request uses TLS is to append “s” to the “http” section of a URL and, voila, you’ve made it more difficult for the previously-mentioned tools to monitor the data.

However, this doesn’t provide perfect protection.

Using Perfect Forward Secrecy

While encrypted traffic is unreadable, IT companies can still store it. If attackers compromise the key that encrypts your traffic, they can use it to read all the previously-stored traffic.

Enforcing TLS with Network Security Configuration

To enforce TLS on Android N and higher, open app/res/xml, where you’ll find an empty file named network_security_config.xml. In this file, add the following code:

<?xml version="1.0" encoding="utf-8"?> 
  <domain-config cleartextTrafficPermitted="false"> 
    <domain includeSubdomains="true"></domain> 
<?xml version="1.0" encoding="utf-8"?>
<manifest package=""
  <!-- // ... -->
    <!-- // ... -->
const val BASE_ENDPOINT = "" 
Figure 17.2 — CLEARTEXT Error Messages
Wasawu 24.1 — XFUOZVENS Ehfog Vuqpisor

const val BASE_ENDPOINT = "" 

Updating Security Providers

Often, when security researchers find vulnerabilities in software, the software company releases a patch. It’s a good idea to make sure you’ve patched the security provider for TLS. If you see an error such as, SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure during your debugging, this usually means you need to update the provider.

Understanding Certificate and Public Key Pinning

Now that you’ve taken the first steps in securing your data, take a moment to consider how HTTPS works.

Implementing Certificate Pinning

Certificate pinning is easy to implement on Android N+. Instead of comparing the entire certificate, it compares the hash (more on this later) of the public key, often called a pin:

Figure 17.3 — Certificate Pinning
Roxipu 50.6 — Salyamimure Popvicx

Figure 17.4 — Insert Your Domain into the SSL Lab Website
Nehexu 90.6 — Azpujd Fuax Fuhuez evtu qza KSZ Ram Gajliya

Figure 17.5 — Select the Server
Pepiha 27.6 — Nixoyl mwu Faklus

Figure 17.6 — Select the Server From
Ruyazo 56.2 — Kanind wti Zepbef Wkuk

<?xml version="1.0" encoding="utf-8"?>
  <domain-config cleartextTrafficPermitted="false">
    <domain includeSubdomains="true"></domain>
    <!-- FROM HERE -->
      <pin digest="SHA-256">U8zLlKBQLcRpbcte+Y0kpfoe0pMz+ABQqhAdPlPtf7M=</pin>
      <pin digest="SHA-256">JSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFA=</pin>
    <!-- TO HERE -->
<pin digest="SHA-256">U8zLlT56PmiT3SR0WdFOR3dghwJrQ8yXx6JLSqTIRpk=</pin> 
<pin digest="SHA-256">JSMzq7xBsOVe1PQRwOsHsw3bsGT2VzIqz5K+59sNQws=</pin> 
Figure 17.7 — Pin Verified Failed
Qekami 47.4 — Zuw Fonuxuaf Ziakag

Implementing Pinning for Early Android Versions

In this app, you’re using OKHttp as the network library. Fortunately, this library lets you add pinning manually.

val hostname = "**" //Double-asterisk matches any number of subdomains.
val certificatePinner = CertificatePinner.Builder()
    .add(hostname, "sha256/U8zLlKBQLcRpbcte+Y0kpfoe0pMz+ABQqhAdPlPtf7M=")
    .add(hostname, "sha256/JSMzqOOrtyOT1kmau6zKhgT676hGgczD5VMdRMyJZFA=")

Using Certificate Transparency

Certificate Transparency is a new standard that audits the presented certificates when you set up an HTTPS connection without requiring hard-coded values in the app.

Implementing Certificate Transparency

In the app module build.gradle, add the following to the list of dependencies and sync Gradle:

implementation "com.appmattus.certificatetransparency:certificatetransparency-android:1.1.1"
import com.appmattus.certificatetransparency.certificateTransparencyInterceptor
val ctInterceptor = certificateTransparencyInterceptor {
  // Enable for the provided hosts
  +"*" //1 For subdomains
  +"" //2 asterisk does not cover base domain
  //+"*.*" - this will add all hosts
  //-"" //3 Exclude specific hosts

Preventing Information Leaks with OCSP Stapling

The traditional way to determine if an entity revoked a certificate is to check a Certificate Revocation List (CRL). To do this, your app must contact a third party to confirm the validity of the certificate, which adds network overhead. It also leaks private information about the sites you want to connect with to the third party.

Understanding Authentication

During World War II, German bombers used Lorenz radio beams to navigate and to find targets in Britain. The problem with this technology was that the British started transmitting their own, stronger, beams on the same wavelength to confuse the Germans. What the Germans needed was some kind of signature to be able to tell the forged beams from the authentic ones. Today, engineers use digital signatures as a more robust way to verify the integrity of information.

Authenticating With Public-Key Cryptography

In many cases, when an API sends data over a network, the data also contains a hash. But how can you use a hash to know if a malicious user tampered with the data? All an attacker would have to do is alter that data and then recompute the hash.

Figure 17.8 — Elliptic Curve Digital Signature Algorithm
Bomoze 78.3 — Oxxamxal Gunko Rukemat Wawyivuqa Ozxikitzp

Verifying Integrity With Elliptic-Curve Cryptography

ECC is a new set of algorithms based on elliptic curves over finite fields. While the math is out of scope for this chapter, you can read more about it here:

Adding Public and Private Keys

Add a public key and private key just after the Authenticator class definition:

class Authenticator {

  private val publicKey: PublicKey
  private val privateKey: PrivateKey
  // ...
class Authenticator {
  // ...
  init {
    val keyPairGenerator = KeyPairGenerator.getInstance("EC") // 1
    keyPairGenerator.initialize(256) // 2
    val keyPair = keyPairGenerator.genKeyPair() // 3

    // 4
    publicKey = keyPair.public
    privateKey = keyPair.private
  // ...

Adding the Sign and Verify Methods

To complete this class, update the sign and verify methods. Replace the contents of sign() with this:

class Authenticator {
  // ...
  fun sign(data: ByteArray): ByteArray {
    val signature = Signature.getInstance("SHA512withECDSA") // 1
    signature.initSign(privateKey) // 2
    signature.update(data) // 3
    return signature.sign() // 4
  // ...
class Authenticator {
  // ...
  fun verify(signature: ByteArray, data: ByteArray, publicKeyString: String): Boolean {
    val verifySignature = Signature.getInstance("SHA512withECDSA")
// 1
    val bytes = android.util.Base64.decode(publicKeyString,
    val publicKey =
    verifySignature.initVerify(publicKey) // 2
    verifySignature.update(data) // 3
    return verifySignature.verify(signature) // 4
  // ...
class Authenticator {
  // ...
  fun publicKey(): String {
    return android.util.Base64.encodeToString(publicKey.encoded, android.util.Base64.NO_WRAP)

Why You Sign a Request

The PetSave app uses test code to simulate connecting to the pet report server via a back-end API. Upon successful submission of the report, the server returns a confirmation code. For your privacy, the test code doesn’t really send your data anywhere. It’s just a simulation. :]

How to Build the Signature

Open MainActivity.kt and search for the line that reads //NOTE: Send credentials to authenticate with server. Here, you’ve logged in to the server with your token and public key. Once the server verifies that info, it returns its public key, which you store in serverPublicKeyString.

Creating the Signature

Back in ReportDetailFragment.kt, add the following code to sendReportPressed(), just under the line that reads //TODO: Add Signature here:

val stringToSign = "$REPORT_APP_ID+$reportID+$reportString" // 1
val bytesToSign = stringToSign.toByteArray(Charsets.UTF_8) // 2
val signedData = mainActivity.clientAuthenticator.sign(bytesToSign) // 3
requestSignature = Base64.encodeToString(signedData, Base64.NO_WRAP) // 4

Verifying the Signature

To verify that your signature is correct, head to ReportManager.kt and look at sendReport(). You’ll find simulated server code that calls serverAuthenticator.verify.

Figure 17.9 — Server Authentication Success
Zojaye 58.5 — Qevfay Eumbicjuruyaoc Rukfacz

bytesToVerify[bytesToVerify.size - 1] = 0 
Figure 17.10 — Server Authentication Failed
Tajaxi 39.18 — Dutyej Ouwnocjenuvaon Rialez

Authenticating the Response

Now that the server has authenticated the report, you also want to authenticate the response so you know the confirmation code, or any other communication from the server, is legitimate. Think of a situation where you’re sending the report to law enforcement — both parties would want to make sure the communication hasn’t been altered.

// 1
val serverSignature = it["signature"] as String
val signatureBytes = Base64.decode(serverSignature, Base64.NO_WRAP)

// 2
val confirmationCode = it["confirmation_code"] as String
val confirmationBytes = confirmationCode.toByteArray(Charsets.UTF_8)

// 3
success = mainActivity.clientAuthenticator.verify(signatureBytes,
    confirmationBytes, mainActivity.serverPublicKeyString)

Testing Your Authentication

To test that it worked, set a breakpoint on the if (success) { line inside onReportReceived(). Build and debug to see the result in the Debug tab.

Figure 17.11 — Authentication Success
Yuboga 69.94 — Aohmuydafoxouh Qaxjurt

confirmationBytes[confirmationBytes.size - 1] = 0 
Figure 17.12 — Authentication Failed
Dokequ 75.66 — Oeqjepyaqapiiw Reusor

End-to-end Encryption

While you’ve secured your connection to a server, the server decrypts the data once it arrives. Sometimes a company needs to see this information, but there’s a recent ethical trend towards end-to-end encryption.

Key Points

In this chapter, you discovered that you should:

Where to Go From Here?

Here are some other points about network safety:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now