A. Key-agreement protocol with QR Codes
To allow users exchange end-to-end encrypted messages, each participant in a conversation must first generate an elliptic curve (EC) keypair for their chosen contact. From the QRSMS app, this can be achieved by going from the inbox screen, select ”Generate QR”, then select or input the contact that this keypair will be created for. This will then forward the user to a screen that shows a QR code that contains the public key from the generated keypair. They can show the QR code to the person, save it to their device or share it via other application (fig. 1).
After receiving their contact’s QR Code, they can scan the given code through their mobile phone’s camera (fig. 2). The scanning process will check if the code is a valid code, and then attempt to initiate an Elliptic Curve Diffie-Hellman (ECDH) key agreement with the said contact. Once this step is done for both users, they can now begin exchanging encrypted messages.
Figure 1: QR Code Generation Process
Figure 2: QR Code Scanning Process
Key pair generation
In order to generate an EC key pair for a given contact, the KeyPairGenerator class from the java.security package was used. Before the final key pair can be obtinaned, this class must first be instantiated and initialized. In the application, doing this actions can slightly differ depending on the device’s API level.
API level 31 and above
If the device’s API level is 31 or higher — that is, starting from android version 12, an instance of the KeyPairGene
rator class can be instantiated with the getInstance() method by adding the algorithm name EC as the first parameter and giving the string AndroidKeyStore in the provider field. The generator is then initialized by building an AlgorithmParameterSpec using the KeyGenParameterSpec.Builder class. An alias which is the identifier for the key when stored inside the AndroidKeyStore system is provided as well as the intended purpose of the key which is available as the constant PURPOSE_AGREE_KEY from the KeyProperties class are added to the builder. This ensures that the key material can only be used for the ECDH key-agreement. The parameter spec for EC was set with the setAlgorithmParameterSpec() method so that the generated key pair will be under the secp256r1 curve. Finally, the generateKeyPair() method of the generator class is invoked which creates a KeyPair entry inside the AndroidKeyStore.
API level 24 to 30
For devices with API level 24 to 30 (Android 7 to 11), the generation process involves the same instantiation and initialization. To get an instance of the generator, the getInstance() method is called with the standard algorithm name added as a parameter. In this case, adding a provider is optional. Similarly, when initializing the generator, only the AlgorithmParameterSpec is needed. A call can then be made to the generateKeyPair() method which returns a KeyPair object. However, because the PURPOSE_AGREE_KEY is only added from API 31 and above, the keypair is manually inserted to a Room database which is an abstraction layer for SQLite [31], and is encrypted using the SQLCipher for android [32] library.
QR Code Generation
Once a key pair is generated, the public key along with the phone number of the user is encoded in a QR code with the format of <phone_number>:<public_key_in_base_64>. Since the public key is a PublicKey object, it must first be encoded into byte array and then converted into a base64 since the qr code generator encodes a string.
QR Code generation was achieved using the com.google.zxing library adding it to the app as a dependency. With this library, the qr code generator can use the qrcode.QRCodeWriter class which contains the encode() method that can be used to generate the QR Code by passing the text to be encoded, format/type of the barcode, width and height, and additional hints that can be added to the QR Code. The encoded QR Code is converted into a Bitmap image so that it can be displayed to the user. This QR code can then be saved to the user’s device or shared via other applications using android Intents.
QR Code Scanning
QR Code scanning capabilities are provided by android’s CameraX with Google’s MLKit analyzer and barcode scanning. The CameraX library enables the app to use the camera and attach its preview to a PreviewView. The CameraX package also provides built-in integration of Google’s MLKit analyzer. By using the setImageAnalysi
sAnalyzer() method, the BarcodeScannerOptions is passed to the MlKitAnalyzer class so that the camera can detect QR Codes and decode its content. If the analyzer detects a valid QR Code, it will then attempt to perform a key exchange.
Key-Agreement
Upon decoding a valid QR code for QRSMS, the application attempts to perform an ECDH key agreement. Since the phone number of the contact is also embedded inside the QR code, the system checks if there is an existing key pair stored in either the room database or the android keystore system depending on the device’s supported API level. If there is, the system performs ECDH key agreement by taking the user’s private key for the contact and their public key as parameters. In the same step, the contact’s public key gets parsed back into a byte array from the base 64 string that it is from the decoding step.
The KeyAgreement class contains the function needed to generate a shared secret key. To use it, an instance of this class was obtained by using the getInstance() method with the key-agreement algorithm name — "ECDH" as the parameter. This instance was initialized by giving the private key of the user as a parameter to the init method. Then, the doPhase() method is invoked with the contact’s public key as the first parameter and a boolean value of true to signify that this is the last phase of the key-agreement. The generateSecret() method finally generates the shared secret key based on the key objects provided in the earlier phases. This secret key is further processed using through a HMAC-based Extract-and-Expand Key Derivation Function (HKDF) by adding the at.favre.lib.hkdf library. A salt based on the phone numbers of the users involved in the exchange along with the generated shared secret key is given to the hkdf extract function to create a pseudorandom key. Before the expand phase, the algorithm string, the user and their contact’s public key bytes are combined to form the info parameter. The pseudorandom key, info, and length of 32-bytes is provided as parameter to the hkdf expand function which outputs the final secret key material of the given length that will be used for the encryption and decryption algorithm. The derived key is then stored inside the AndroidKeyStore for encryption and decryption, using the contact’s phone number as the key’s alias.
B. SMS Client
While the generation and scanning of QR code is described in the previous sections, the client also provides users the ability to send and read regular and encrypted messages. Messages are retrieved from the default SMS application’s content provider to ensure that the built-in SMS application remains operational even with QRSMS installed. To send a message, the user can either select a conversation from the inbox screen (fig. 3) which will take the user to the conversation screen that displays the thread of past messages, or select the create message icon in the lower right portion of the screen. Using the new conversation approach moves the user to a screen where they can select a contact or enter the phone number of the recipient (fig. 4).
Figure 3: QRSMS Inbox Screen
Figure 5: QRSMS New Conversation Screen
In the conversation screen, if the user has an existing secret key with the contact (e.g. they have previously performed a key-agreement), a select message type icon appears in front of the message input box which will show a dialog where the user can select either to send a regular or an encrypted SMS. Regardless of what type of message the user chooses to send, sending the message is done through the SmsManager class that the android system provides. However, choosing to send an encrypted SMS message includes additional processing to the text before sending it to the recipient (fig. 6). On the other hand, decryption of SMS message is done automatically by the application such that when the user opens a conversation that includes an encrypted text, the application will automatically attempt to decrypt the message, given that the key used to encrypt the message is available (fig. 7). If the same message is attempted to be viewed from the default SMS app, or if the corresponding key is not available (e.g the key is deleted, the user has not scanned the code for that specific contact), users will only see a string of text in base 64 (fig. 8).
The app also provides a way to view and manage existing keys. The key management screen can be accessed by pressing the vertical ellipsis at the top right of the screen which brings out a menu where Manage Keys can be selected (fig. 9). Users can view the QR code which forwards them to the screen displayed after generating a QR Code and also a delete button. The screen also gives indication if end-to-end encryption is available for a given contact.
Figure 6: Sending Encrypted Message
Figure 7: Reading Encrypted Message
Figure 8: Encrypted message as viewed in default sms app
Figure 9: Key Management Screen
Message Encryption
If the user choose to send an encrypted message, the text is encrypted using the Cipher class from the javax.crypto package. Android supports the "AES/CBC/PKCS7Padding" transformation which is used to create an instance of the cipher class. It is then initialized using the init() method providing the ENCRYPT_MODE constant to indicate that the cipher object is used for encryption as well as the secret key object obtained from Android KeyStore. The doFinal() method encrypts the message and returns the ciphertext in byte array. The cipher object also automatically generates an initialization vector (IV) for each encryption. The IV is prepended before the cipher text and converted into base 64 string. Before sending the encrypted message to the receiver, the author chose the Start of Heading + Start of Text unicode characters to indicate the beginning of the encrypted message and the End of Text + End of Transmission characters to indicate the end. This characters are also converted to base 64 so that the SMS message will not be converted to UTF-8 encoding which reduces the maximum length of a message from 160 to just 70.
Message Decryption
Decryption of a message is done automatically when a conversation is opened. When rendering a message to the screen, the application checks if the current message is encrypted. Encrypted messages are identified by checking if the given text has the prepended and appended characters. If it is, the flags are striped from the ciphertext. Before initializing the cipher object, the IV must be extracted from the base 64 string and then reconstructed using the IvParameterSpec. With the IV extracted, the cipher object can now be initialized with DECRYPT_MODE as its mode of operation, the secret key shared with the contact, and the extracted iv. The doFinal() method can then be performed on the cipher text which will decrypt the given message.