Overcome The javax.crypto.IllegalBlockSizeException In Android for Asymmetric Communication With A Host PHP Server

I need to start my stating I am far from an expert. I am learning encryption as I go. Read, trial, error, read, etc. There is a lot of information out there, so please do your own research. I am just showing you my successes through failures.

I am learning about secure communication with a host server and Android. My last few posts covers various aspects of encryption I have been working on and educating myself about.

This post will show you how I was able to work through am issue I had with asymmetric encryption.

Notes

Just a little information before I get started.

Asymmetric encryption utilizes block sizes based on the key size. If you have a bigger key size, then you would be able to communicate with larger block sizes, data packets. There is an issue with this, performance. The process to decrypt with a larger key size has a toll on performance with the host, server. This is not good.

Symmetric on the other hand can handle bigger pieces of data to encrypt/decrypt. The issue with symmetric encryption is that the same key is used to encrypt and decrypt. If either key is compromised, then the data can be comprised.

This is where hybrid encryption is used.

  1. The client encrypts the data with the symmetric encryption key
  2. The client then encrypts the recently encrypted data again with asymmetric encryption using the public key they have
  3. The data is sent to the host, server
  4. The host then decrypts the data with asymmetric encryption using their private key
  5. The data is then again decrypted with the symmetric encryption key

This does seem to be a lot but it gives better security than just symmetric encryption and allows more data to be able to be sent.

BTW, my example below is just demonstrating overcoming the block size error in asymmetric encryption. I may plan on post the whole encryption process at a later date.

Issues

The issue I had with the error

javax.crypto.IllegalBlockSizeException

I still had some issues with dealing with a larger data size than what the testing algorithm allowed.

With this, I needed to break up the data somehow to allow me to send them from Android to the server.

The error I was getting was stating the data needed to be no larger than 64 bytes. After research, I found that the data actually needed to be less than that to account for padding.

64 bytes – 11 for padding = allowed data

Below will show you how I implemented this in Android as well as some of my PHP code on the server side to receive the data, decrypt it and display.

Code

This is the Android class I created to create the data chunks. If you look at it, I added a “:” delimiter to allow me to piece the data together to be sent. This would also allow me to easily split the message at the server.

object SecureMessage {

    fun getTheKey(pk : String) : String {
        val key = pk.replace("\\r".toRegex(), "")
            .replace("\\n".toRegex(), "")
            .replace(System.lineSeparator().toRegex(), "")
            .replace("-----BEGIN PUBLIC KEY-----", "")
            .replace("-----END PUBLIC KEY-----", "")
            .trim()
        Log.d("Public Key", key)
        return key
    }

    fun encryptData(txt: String, publicKey: String): String? {
        val pk = getTheKey(publicKey)
        var encoded = ""
        var encodedStr = StringBuilder()
        var encrypted: ByteArray? = null
        try {
            val publicBytes: ByteArray = Base64.decode(pk, Base64.DEFAULT)

            val keySpec = X509EncodedKeySpec(publicBytes)
            val keyFactory: KeyFactory = KeyFactory.getInstance("RSA")
            val pubKey: PublicKey = keyFactory.generatePublic(keySpec)
            val cipher: Cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING")
            cipher.init(Cipher.ENCRYPT_MODE, pubKey)

            val encryptTxt = txt.encodeToByteArray()
            Log.d("Encrypt Text", "Size of text to encrypt: " + encryptTxt.size.toString())

            val blockSize = 53
            val convertArray = arrayListOf<ByteArray>()
            val encryptedArray = arrayListOf<ByteArray>()
            var i = 0
            var index = 0
            var dstSize = blockSize
            if(encryptTxt.size < blockSize){
                encrypted = cipher.doFinal(encryptTxt)
                encoded = Base64.encode(encrypted, Base64.DEFAULT).decodeToString()
                return encoded
            }
            while (i < encryptTxt.size-1){
                Log.d("Encrypt Text", "$i : $dstSize")
                val dst = ByteArray(dstSize)
                while(index < dstSize){
                    dst[index] = encryptTxt[i]
                    index++
                    i++
                }
                index = 0

                convertArray.add(dst)
                encrypted = cipher.doFinal(dst)
                encryptedArray.add(Base64.encode(encrypted, Base64.DEFAULT))
                Log.d("Encrypt Text", "Sample: " + encryptedArray.get(encryptedArray.size-1).decodeToString())
                if(encryptTxt.size - 1 - i < blockSize){
                    dstSize = encryptTxt.size - i
                }
            }

            encodedStr.apply {
                encryptedArray.forEach {
                    if(this.isNotEmpty()){
                        this.append(":")
                    }
                    this.append(it.decodeToString().trim())
                }

            }
            return encodedStr.toString()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return encoded
    }
}

To perform the encryption you can call the following where you want.

//this is for testing - the key should be in a better place
val inputStream: InputStream = applicationContext.resources.openRawResource(R.raw.pk)
                    val bytes: ByteArray = inputStream.readBytes()
                    val pk = String(bytes)
                    Log.d("Public Key", pk)
                    val encryptedData = SecureMessage.encryptData(message, pk)
                    if (encryptedData != null) {
                        Log.d("Encrypted Data", encryptedData)
                    }

For communication with the server, I am using Retrofit. I will not go through the setup, just the main methods used to call sending the data to the server

This method is located in my viewmodel

    fun testDecrypt(message : String) {
        val secureMsg = processEncryptData(message)
        if(secureMsg == null) return

        val query = RetrofitHelper.getInstance(applicationContext).create(AccessApi::class.java)
        val call: Call<String> = query.testDecrypt(SecureDataMessage(secureMsg))
        call.enqueue(object : Callback<String> {
            override fun onFailure(call: Call<String>, error: Throwable) {

            }

            override fun onResponse(
                call: Call<String>,
                response: Response<String>
            ) {
                val result = response.body()
                if (result != null) {
                    Log.i("Result", result)
                }
            }
        })
    }

The method above utilizes a custom data type named SecureDataMessage which is posted below

data class SecureDataMessage (

    @SerializedName("message")
    val message : String

)

AccessAPI is the interface used in Retrofit to call the required classes with the http parameters

    // your Retrofit singleton should hold root url - this just shows which page to query
    @POST("test_decrypt_page.php")
    fun testDecrypt(@Body message : SecureDataMessage) : Call<String>

That is all on Android, now for the PHP server script to receive and decrypt the message.

The message I created to send was, I’m pointing this out because the bottom of the PHP script shows it looking for the JSON item “message”.

{"message":"This is an important message","id":1234567890,"mood":"Happy!"}
<?php

$data = json_decode(file_get_contents('php://input'));

$test_pk = "-----BEGIN PRIVATE KEY-----
....
please find a good place for your key, I put it here while testing theory
....
-----END PRIVATE KEY-----";

//get object 'message' because that is the data type in Android used to send to server...remember SecureDataMessage class

$message = $data->{'message'};

//find and split the encryption data using the delimiter into an array

$dataArr = explode(":", $message);

$str = "";

//loop through array and build a string after each item is decrypted
for($i = 0; $i < count($dataArr); $i++){

    openssl_private_decrypt(base64_decode($dataArr[$i]), $test_decrypted, $test_pk);

    $str .= $test_decrypted;

}

$str = mb_convert_encoding($str, "UTF-8");

$json = json_decode($str, true);

$msg = $json['message'];

echo $msg;

BTW please go through some error checking in this PHP script. I didn’t add any. I whipped it up quickly to test functionality.

That’s it for the implementation. Hope this was useful.


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *