Guide Area
Guidearea banner

Create a REST web service in PHP – part II (SSL and security)

This article will describe three different ways to secure your web service – free SSL, paid SSL and custom encryption system.

In the first part of the PHP Webservice Tutorial I showed you how to create a very simple web service. I assume that if you’re reading this part of the tutorial too, you would probably like to see how the custom security is done.

SSL certificates are a very easy way to secure communication between client and server. Although, it might be expensive for you to pay 60-100$ for a yearly subscription just to be able to use the HTTPS protocol.

Free SSL certificate

There is a way to achieve the security for free. A website letsencrypt.org lets you create a free certificate that you can use for anything you want. Even though you surely think this is awesome, I have to calm you down a bit. From my personal experience (tried it twice with two different projects), it isn’t always working properly. During the two projects I ran across two problems – internet browser (Chrome) did not trust the certificate, so instead of displaying the web page instantly, it took me to the red screen with security warning.

www.inmotionhosting.com

The second problem appeared when I tried to connect to my webservice from Java’s HTTPSUrlConnection object. This class did not trust the certificate and although there is a workaround (something about temporarily setting Java to trust ANY of the incoming certificates – I do not recommend this approach), it’s a big struggle to make this work properly with respect to security and proper workflow.

Paid SSL certificate

During my projects I could not find any SSL comparison worth checking out. All the websites promote their own certificates or help their partner websites to promote. For a list of certificate providers, you can visit this site:

https://www.thoughtco.com/cheap-ssl-certificates-and-recommendations-3469539

In case you’re a student, you can get a GitHub Student pack which includes a bunch of useful tools and tutorials – including a one-year free SSL certificate.  I have tried it and it’s worth trying. The certificate is easy to use, ready in a very short amount of time, and the technical support of namecheap.com is excellent. I would choose this provider for the next paid year as well.

Alternative solution – Custom encryption

If you’re up for a free solution that will work based on how you program it, you can create your own data protection. This is what I have done during my internship at one of the local Danish companies. The budget was limited but our customer preferred to have secure data transmission, so we came up with a solution that would meet the requirements.

There are two types of encryption methods that we combined into one. The first was RSA (read more here). This encryption is used in SSL certificates and works with a pair of keys, public and private. What happens is that when you connect your client to the server, the client has to accept the certificate in order to safely communicate with the server. Server generates a public key for each client separately or the same public key for all clients, in case you have just one of those and you know that this fact will never change (this was our case).

The second method we used was AES (read more here). The difference between RSA and AES is that RSA is more secure thanks to maximum size of the keys (up to 4096). On the other hand, AES provides faster encryption speeds. This combination is perfect if you’re using RSA for secure handshake and AES for encryption and transmission of huge amount of data. Hence, if you transfer just a small pieces of information, go for plain RSA integration.

There is one flaw in this solution. It will never be perfectly secure if you do a “handshake” on public network, such as WiFi in your coffee shop. By connecting on a public non-trusted network you risk that your certificate will be caught by a MITM (Man in the middle, read more here) and will be used shortly after that to gain access to your encryption keys, hence your data. 

Workflow

Imagine that you have a web service with one simple action and in order to access this action, you have to log in using your username and password. When system receives the POST request, it check the database for existing user and if found, it gives back the return of the simple action.

The way this solution works is that when the client connects to the server for the first time, he receives the servers public key. The public key is saved and the data are analyzed. Before you send a POST request from your client, a new AES pair of keys is generated for data encryption. This situation happens every time you send the request.

When you create the POST request, you need to verify your account. The request will have two different parameters, let’s call them ‘data1’ and ‘data2’. The ‘data1’ represents the username, password and two AES keys. These information will be put into a JSON object which will serve the parameter as a value. The whole parameter will be encrypted using RSA.

The second parameter, ‘data2’, will contain all the other information, such as parameters for the simple web service cation, or any other required values. It will, again, be put into a JSON object. This object will be encrypted using AES keys from the RSA-encrypted parameter ‘data1’. The final POST request will look similar to this table:

RSA and AES POST request

When the server receives this request, it will use it’s private key to decrypt the ‘data1’. It now has access to the login credentials and AES keys for this specific request. It verifies if the specific user exists in database. In case the user does exist, the server uses the AES keys to decrypt the ‘data2’, from which it now gets the required action and other parameters required for completion of the simple web service action.

Because only the client and the server have the current AES keys, the web service response does not necessarily need to be encrypted by RSA anymore. Of course, this depends on the type of data you’re returning. If your response includes user passwords or other similarly sensitive data, you might want to add some extra protection.

AES POST response

When client receives the response, it decrypts its contents and generates two new AES keys for the next web service request.

Code – PHP

I’m going to assume that you generated your server’s public and private keys and stored them outside of your public_html (or www, or htdocs) directory. For AES encryption, I used Sabin Jose’s answer from stackoverflow (which can be found here) and made small modifications. The source code:

<?php

class MCrypt
{
    private $iv = "";
    private $key = "";

    function __construct($iv, $key)
    {
        $this->iv=$iv;
        $this->key=$key;
    }


    function encrypt($str) {
        $iv = $this->iv;
        $td = mcrypt_module_open('rijndael-128', '', 'cbc', $iv);
        mcrypt_generic_init($td, $this->key, $iv);
        $encrypted = mcrypt_generic($td, $str);
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        return bin2hex($encrypted);
    }

    function decrypt($code) {
        $code = $this->hex2bin($code);
        $iv = $this->iv;
        $td = mcrypt_module_open('rijndael-128', '', 'cbc', $iv);
        mcrypt_generic_init($td, $this->key, $iv);
        $decrypted = mdecrypt_generic($td, $code);
        mcrypt_generic_deinit($td);
        mcrypt_module_close($td);
        return utf8_encode(trim($decrypted));
    }

    protected function hex2bin($hexdata) {
        $bindata = '';
        for ($i = 0; $i < strlen($hexdata); $i += 2) {
            $bindata .= chr(hexdec(substr($hexdata, $i, 2)));
        }
        return $bindata;
    }
}

The code for RSA was written by me and it includes three functions – one for giving the client my public key at handshake, one for decrypting request parameters from the client and the last one for formatting of public keys format for Java client.

function getPublicRsaKey()
{
    $returnString = file_get_contents('your_path_to_keys/ws_publicKey.pem', FILE_USE_INCLUDE_PATH);
    $returnString = str_replace("-----BEGIN PUBLIC KEY-----","",$returnString);
    $returnString = str_replace("-----END PUBLIC KEY-----","",$returnString);
    return removenewline($returnString);
}

function decrypt_with_rsa_prk($string)
{

$prkString = file_get_contents('your_path_to_keys/ws_privateKey.pem', FILE_USE_INCLUDE_PATH);
$prk = openssl_get_privatekey($prkString);

    $return_string = "";
    openssl_private_decrypt($string, $return_string, $prk);
    return $return_string;
}
function removenewline($string){
    $tempstring = str_replace(array("\r", "\n"), '', $string);
    $tempstring = str_replace(array("\/"), '/', $tempstring);
    return $tempstring;
}
There’s many certificate encoding types, many certificate formats and many ways to generate certificates. This tutorial will not work for all of you. The main point of it is to show you the idea, its workflow and steps, so you can create your own security transfer. You can also copy-paste my code and try to brainstorm, there is a lot of solutions for encryption issues on the internet.

The final step is to put the code below to the web service PHP file that you will send your POST request to:

if (!isset($_POST['data1'])) {
    // For the simulation purposes, we will return the public key
    // anytime the client sends an empty request
    echo getPublicRsaKey();
} else {
    // decoding data1
    $string = base64_decode($_POST['data1']);
    $jsonArray = json_decode(decrypt_with_rsa_prk($string), true);

    // getting data1 parameters
    $usrname = $jsonArray[0]["username"];
    $password = $jsonArray[0]["password"];
    $iv = $jsonArray[0]["iv"];
    $key = $jsonArray[0]["key"];

    ... your login procedures here ...

    if (... login successful ...) {
        // check if there is a data2 and decode it with key and iv from data1
        $data2string = decrypt_with_aes($_POST['data2'], $key, $iv);
        $jsonArray = json_decode($data2string, true);

        ... your actions with data2 contents ...
        // encrypt web service response with given AES key and init.vector.
        $encodedreply = encrypt_with_aes(... whatever you want to return ..., $key, $iv);

        echo $encodedreply;
    } 
}

And that is pretty much it. This is a modified simulation of my code, you might want to change it so it serves your purposes, but at the end, it will work the same way.

Code – Java (Android)

In order to start, we have to get a public key from the server. You have a pre-made PHP function for that and from my previous tutorial you should be able to get it to Java. I assume that you already have your public key on the client side, so I will skip right to the encryption. We will again use the solution that Sabin Jose posted on stackoverflow (link above), although there will be small modifications that I made for this tutorial.

private String iv = "";
private IvParameterSpec ivspec;
private SecretKeySpec keyspec;
private Cipher cipher;

private String SecretKey = "";

public MCrypt() {
    iv = getRandomHexString(16);
    SecretKey = getRandomHexString(16);
    ivspec = new IvParameterSpec(iv.getBytes());
    keyspec = new SecretKeySpec(SecretKey.getBytes(), "AES");
    try {
        cipher = Cipher.getInstance("AES/CBC/NoPadding");
    } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
        e.printStackTrace();
    }
}

private String getRandomHexString(int numchars) {
    Random r = new Random();
    StringBuffer sb = new StringBuffer();
    while(sb.length() < numchars) {
        sb.append(Integer.toHexString(r.nextInt()));
    }
    return sb.toString().substring(0, numchars);
}

public byte[] encrypt(String text) throws Exception {
    if(text == null || text.length() == 0)
    throw new Exception("Empty string");
    byte[] encrypted = null;

    try {
        cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
        encrypted = cipher.doFinal(padString(text).getBytes());
    } catch (Exception e) {
        throw new Exception("[encrypt] " + e.getMessage());
    }
    return encrypted;
}

public byte[] decrypt(String code) throws Exception {
    if(code == null || code.length() == 0)
        throw new Exception("Empty string");

    byte[] decrypted = null;

    try {
        cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
        decrypted = cipher.doFinal(hexToBytes(code));
    } catch (Exception e) {
        throw new Exception("[decrypt] " + e.getMessage());
    }
    return decrypted;
}

public static String bytesToHex(byte[] data) {
    if (data==null) {
        return null;
    }

    int len = data.length;
    String str = "";
    for (int i=0; i<len; i++) {
        if ((data[i]&0xFF)<16)
            str = str + "0" + java.lang.Integer.toHexString(data[i]&0xFF);
        else
            str = str + java.lang.Integer.toHexString(data[i]&0xFF);
    }
    return str;
}
public static byte[] hexToBytes(String str) {
    if (str==null) {
        return null;
    } else if (str.length() < 2) {
        return null;
    } else {
        int len = str.length() / 2;
        byte[] buffer = new byte[len];
        for (int i=0; i<len; i++) {
            buffer[i] = (byte) Integer.parseInt(str.substring(i*2,i*2+2),16);
        }
        return buffer;
    }
}

private static String padString(String source) {
    char paddingChar = ' ';
    int size = 16;
    int x = source.length() % size;
    int padLength = size - x;

    for (int i = 0; i < padLength; i++) {
        source += paddingChar;
    }

    return source;
}

public String getIv() {
    return iv;
}

public String getKey() {
    return SecretKey;
}

We also need an RSA method that will convert the public key saved somewhere in our client from String to RSAPublicKey format.

public static RSAPublicKey getPublicKeyFromString(String key) throws IOException, GeneralSecurityException {
    String publicKeyPEM = key;
    publicKeyPEM = publicKeyPEM.replace("-----BEGIN PUBLIC KEY-----\n", "");
    publicKeyPEM = publicKeyPEM.replace("-----END PUBLIC KEY-----", "");
    byte[] encoded = Base64.decode(publicKeyPEM, Base64.DEFAULT);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    RSAPublicKey pubKey = (RSAPublicKey) kf.generatePublic(new X509EncodedKeySpec(encoded));
    return pubKey;
}

And now we’re good to go. Let’s send a message to the server:

byte[] encodedBytes = null;

JSONArray jsonArrayData1 = new JSONArray();
JSONArray jsonArrayData2 = new JSONArray();

mc = new MCrypt();
// creating new json object for user validation and providing server with AES key and initialization vector
JSONObject jsonObjectData1 = new JSONObject();
jsonObjectData1.put("username",username);
jsonObjectData1.put("password",password);
jsonObjectData1.put("key",mc.getKey());
jsonObjectData1.put("iv",mc.getIv());

// recently created json object needs to be added to json array as web service accepts json arrays
jsonArrayData1.put(jsonObjectData1);

// any values that you want to post to web service as data2 values
JSONObject jsonObjectData2 = new JSONObject();
jsonObjectData2.put("action","simplewebserviceaction");

// just created json object needs to be added to json array as web service accepts json arrays
jsonArrayData2.put(jsonObjectData2);

// encrypting with servers RSA public key
Cipher c = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");
c.init(Cipher.ENCRYPT_MODE, puk);
encodedBytes = c.doFinal(jsonArrayData1.toString().getBytes());

// the final data1 and data2 post parameter
String encodedData1 = Base64.encodeToString(encodedBytes, Base64.DEFAULT);
String encodedData2 = MCrypt.bytesToHex(mc.encrypt(jsonArrayData2.toString()));

All you have to do now is to send these two strings to your web service as ‘data1’ and ‘data2’ parameters. When the response comes, you need to decrypt it using AES keys that you generated while sending a request.

try {
String newresult = new String(mc.decrypt(result));
} catch (Exception e) {
e.printStackTrace();
}

// need to strip unnecessary symbols from the back, as some were added (found during testing)
int pos = newresult.lastIndexOf("]");
newresult =newresult.substring(0,pos+1);
System.out.println("Reply decrypted ---> " +newresult);

Conclusion

And this is the end of my tutorial. Again, I’m absolutely sure that this solution will not work properly for all of you. There is a lot of things to consider, plan and design when implementing this solution, and it also works differently on every system (I used Linux as my server’s OS). I hope you got an idea of how to create a secure transmission for free. I also hope that it is readable and not that hard to understand. If you have any questions regarding the idea or the workflow, please leave a comment and I will get back to you asap!

Vladimir Marton

DevOps Engineer focused on cloud infrastructure, automation, CI/CD and programming in Javascript, Python, PHP and SQL. Guidearea is my oldest project where I write articles about programming, marketing, SEO and others.