From providing a simple and low cost entry system for public transport, to facilitating peer-to-peer payments between two unconnected parties, using sound to transfer data can bring unique benefits to many different applications. However, one might ask, can you securely send data using sound? Or can a malicious eavesdropper easily intercept the transmissions?

You absolutely can, as Chirp only provides the transport layer, standard encryption techniques can be applied to the data to protect it from any eavesdroppers during transmission. There are several different encryption techniques that could be applied here, but in this tutorial, we will explore the use of AES encryption in iOS (Swift) and Android (Kotlin).

AES (Advanced Encryption Standard) is one of the most popular encryption algorithms, and has been proven to be secure. It was adopted by the US government to protect classified information, and is also used in secure file transfer protocols such as HTTPS and SSH. AES's block cipher method is also particularly useful here as it doesn’t increase the size of the payload, which is handy for the low bandwidth channel that audio provides.

You can view the source code for an encrypted version of our Chirp Messenger app for iOS and Android, or read on to see a simple example of using AES encryption with Chirp. This tutorial assumes that you have already followed the getting started guides at the Chirp Developer Hub.

Example application to send encrypted messages

AES Encryption

The overall approach to carrying out encryption and decryption is as follows:

  • Pick a key to use on both the sender and receiver: the length of the key, in bits, will determine which AES version will be used: 128, 192 or 256 bits. Greater values have a higher complexity to attack.
  • Provide an IV (initialisation vector) or a counter: this value must be different for each payload you encrypt, otherwise you will open yourself to attack. The way the IV is modified must be known by both parts and replicable, as you can only decrypt the data with the exact same IV.
  • Call the encryption / decryption functions on your data: these methods will return the encrypted/decrypted payload, which will be the same length as the raw payload.

iOS

To encrypt data on iOS we will use the CryptoSwift library. You can install this using CocoaPods or just copy the files from GitHub to your project.

import ChirpConnect
import CryptoSwift

func generateIv() -> Array<UInt8> {
    let date: Date = Date()
    let hour = Calendar.current.component(.hour, from: date)
    let minute = Calendar.current.component(.minute, from: date)
    var preHash = Array<UInt8>()
    preHash += self.key
    preHash.append(UInt8(hour))
    preHash.append(UInt8(minute))
    let hash = preHash.sha256()
    let iv: Array<UInt8> = Array<UInt8>(hash[0...15])
    return iv
}

func sendData(data: Data) {
    let iv: Array<UInt8> = self.generateIv()
    let encrypted = Data(try! AES(key: self.key, blockMode: CTR(iv: iv), padding: .noPadding).encrypt(data.bytes))
    if let error = chirp.send(encrypted) {
        print(error.localizedDescription)
    }
}

Here we generate an initialisation vector using the current time, and send our encrypted data using the Chirp SDK. See how to decode received data below.

chirp.receivedBlock = {
    (data : Data?, channel: UInt?) -> () in
    if let data = data {
        let iv: Array<UInt8> = self.generateIv()
        let decrypted = Data(try! AES(key: self.key, blockMode: CTR(iv: iv), padding: .noPadding).decrypt(data.bytes))
        if let payload = String(data: decrypted, encoding: .ascii) {
            print(String(format: "Received: %@", payload))
        } else {
            print("Failed to decode payload!")
        }
    } else {
        print("Decode failed!")
    }
}

Android

For Android, we can use the cryptography functionality provided by the system itself.

import java.security.Security;
import java.util.*

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

fun generateIv(key: ByteArray): ByteArray {
    val now = Date()
    val calendar = Calendar.getInstance()
    calendar.time = now
    val minutes = calendar.get(Calendar.MINUTE)
    val hour = calendar.get(Calendar.HOUR_OF_DAY)

    var keyBytes = key.plus(hour.toByte())
    keyBytes = keyBytes.plus(minutes.toByte())
    val digest = MessageDigest.getInstance("SHA-256")
    val hash = digest.digest(keyBytes)
    return hash.slice(0..15).toByteArray()
}

fun encryptBytes(bytes: ByteArray) : ByteArray {
    val key = SecretKeySpec(keyBytes, "AES")
    val ivBytes = generateIvBytes(keyBytes)
    val ivSpec = IvParameterSpec(ivBytes)

    cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec)
    val bIn = ByteArrayInputStream(bytes)
    val cIn = CipherInputStream(bIn, cipher)
    val bOut = ByteArrayOutputStream()

    var ch: Int = cIn.read()
    while (ch >= 0) {
        bOut.write(ch)
        ch = cIn.read()
    }

    return bOut.toByteArray()
}

fun sendData(data: String) {
    val encryptedData = encryptBytes(data.toByteArray(Charsets.UTF_8))
    val error = chirp.send(encryptedData)
    if (error.code > 0) {
        Log.e("ChirpError: ", error.message)
    }
}

Again, we generate the initialisation vector using the current time, and send our encrypted data using the Chirp SDK. See how to decode received data below.

fun decryptBytes(bytes: ByteArray?) : ByteArray {
    val key = SecretKeySpec(keyBytes, "AES")
    val ivBytes = generateIvBytes(keyBytes)
    val ivSpec = IvParameterSpec(ivBytes)

    cipher.init(Cipher.DECRYPT_MODE, key, ivSpec)
    val bOut = ByteArrayOutputStream()
    val cOut = CipherOutputStream(bOut, cipher)
    cOut.write(bytes)
    cOut.close()
    return bOut.toByteArray()
}

chirp.onReceived { data: ByteArray?, channel: Int ->
    if (data == null) {
        Log.e("Decode failed.")
    } else {
        val message = String(decryptBytes(data), Charsets.UTF_8)
        Log.v(TAG, "Chirp: onReceived: $message on channel: $channel")
    }
}

You will notice that we select a new initialisation vector (IV) before each transmission, as using the same IV significantly weakens the security of the algorithm. Here, we do this by simply hashing the current timestamp, which requires both devices to have the correct time, and also to not cross into the next minute during the transmission! You should decide on your own method to choose an initialisation vector, and combine this with a secret key known only by your applications.


You can view the source code of our modified versions of the Chirp Messenger with AES encryption enabled for iOS and Android at our GitHub example repos. Hopefully this example shows illustrates the simplicity of sending secure data with Chirp.