Android Asymmetric Encryption With Hosted PHP Server

In this post I will show you the method I used to create data encryption with a remote PHP server.

I will not dive into how to send the data. I am just focusing here on how to encrypt data in Android using a public key to then send to a hosted PHP server to decrypt using a private key.

This is just proof of concepts to help people understand how to implement. Please add best practice and security measures to your own design.

Code

In Android, I am showing you a simple example of a method to encrypt the data. The public key here is saved in the project in the res/raw folder. Not best practice but just as I stated, this is proof of concept.

The Android code is based on one of the answers in this stackoverflow question. This was extremely useful.

https://stackoverflow.com/questions/43532954/android-encrypting-string-using-rsa-public-key

I created a class file named SecureMessage

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 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)
            encrypted = cipher.doFinal(txt.toByteArray())
            encoded = Base64.encodeToString(encrypted, Base64.DEFAULT)
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return encoded
    }
}

Now to call this, we implement a method as shown below. I am using Compose and calling it through a LaunchEffect on startup. Again, not best practice, this is just to ensure I can encrypt and decrypt data.

LaunchedEffect(Unit) {

        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("Hello World!", pk)
        if (encryptedData != null) {
            Log.d("Encrypted Data", encryptedData)
            //take the encryptedData string and paste it in the $data variable on the PHP server. This validates proof of concept.
        }
        
}

Now the PHP code to place on the hosted server. You can place this where you need it. I am just testing so I created a separate test file to retrieve the data.

<?php

//.....

$data = "<encrypted data from Android Logcat>";

$test_pk = "-----BEGIN PRIVATE KEY-----
.....
PRIVATE KEY HERE - AGAIN, JUST FOR TESTING PURPOSES
.....
-----END PRIVATE KEY-----";

openssl_private_decrypt(base64_decode($data), $test_decrypted, $test_pk);

echo "This is the decrypted text:<br />";
var_dump($test_decrypted);
//........

?>

Hope this helps people move in the right direction with their encryption. Use this information to create forward momentum with your project but I am not an encryption expert so please do your research.

BONUS

  1. I have a few more things to share. As I am working with decrypting the data packet on the PHP server with the private key, I messed around with different methods of receiving the data and how to decrypt.

If you just are testing both keys in PHP, use this sample. This will create the public key and private keys, encrypt the $test JSON array with the public key, decrypt with the private key then finally display the $test JSON back again.

$test = json_encode(array('id' => 'Jack', 'username' => 'JackHollow'));

// Configuration settings for the key
$config = array(
    "digest_alg" => "sha1",
    "private_key_bits" => 512,
    "private_key_type" => OPENSSL_KEYTYPE_RSA,
);

// Create the private and public key
$res = openssl_pkey_new($config);

// Extract the private key into $private_key
openssl_pkey_export($res, $private_key);

// Extract the public key into $public_key
$public_key = openssl_pkey_get_details($res);
$public_key = $public_key["key"];

echo "Public Key:<br />";
echo $public_key;

echo "<br /><br /><br />";
echo "Private Key:<br />";
echo $private_key;
echo "<br /><br /><br />";

// -------------------------Encrypt using the public key
openssl_public_encrypt($test, $encrypted, $public_key);

$encrypted_hex = bin2hex($encrypted);
echo "This is the encrypted text: $encrypted_hex\n\n";

// -------------------------Decrypt the data using the private key
openssl_private_decrypt($encrypted, $decrypted, $private_key);

echo "#1 Method - This is the decrypted text: $decrypted\n\n";

2. This is the method I showed above in the main post. The data is encrypted using the public key in Android. The data is sent to the logcat. Copy/Paste from logcat to this PHP script as the $data variable. The PHP script then decrypts it using the private key.

$data = ""; //<--- message created in Android - ex. {"message":"Decrypted Message From Android!"}


$test_pk = "-----BEGIN PRIVATE KEY-----
....
<generated private key that matches the public key, can use script #1 to create both to sample>
....
-----END PRIVATE KEY-----";

openssl_private_decrypt(base64_decode($data), $test_decrypted, $test_pk);

echo "<br /><br /><br />";
echo "#2 - This is the decrypted text:$test_decrypted<br />";
echo "<br /><br /><br />";

//---------------#2 Works

3. This was just me messing around and learning more about the process. Still worth posting because this shows an issue I had. The script just takes the string $tryit and decodes it to JSON so I can access each by their key.

The issue I had here was the string wasn’t able to go straight to json_decode. I needed to use the mb_convert_encoding to put the string as UTF-8 for the json_encode function. Took a while to figure out. Useful if anyone needs it.

$tryit = "{\"message\":\"Happy Coding\"}";

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

$json = json_decode($tryit, true);

echo "#3 - This is the decrypted text:{$json['message']}<br />";
var_dump($json);

4. Last method here gives the idea of how to receive the data. For testing I am using the $_GET[”] method so I can alter and test data as much as needed to validate functionality.

Here I took the encrypted data message from the Android logcat ( similar to method #2 ) and pasted it in the url as

<serverAddress>/index.php?data=<encryptedData>

Alter the above with your server address and where the below php script will reside, i.e. index.php. The GET parameter starts with ?data=

$data = $_GET['data'];

$test_pk = "-----BEGIN PRIVATE KEY-----
....
<generated private key that matches the public key, can use script #1 to create both to sample>
....
-----END PRIVATE KEY-----";

openssl_private_decrypt($data, $test_decrypted, $test_pk);

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

$json = json_decode($test_decrypted, true);

echo "<br /><br /><br />";
echo "$4 - This is the decrypted text:{$json['message']}<br />";
echo "<br /><br /><br />";

Finally, if you needed to get the data packet from an app being sent through POST, this is an example. By the way, the app is on Android using Retrofit for communication.

//the data in this example is sent in this format - {"message":<encrypted data string>}

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

$test_pk = "-----BEGIN PRIVATE KEY-----
....
<generated private key that matches the public key, can use script #1 to create both to sample>
....
-----END PRIVATE KEY-----";

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

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

openssl_private_decrypt(base64_decode($data_str), $test_decrypted, $test_pk);

$json = json_decode($test_decrypted, true);

Comments

Leave a Reply

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