Encryption between Java/Android and PHP

Use case

While working on an android app that retrieves it’s data from a PHP-API  I needed to build a both-sided en- and decryption layer to secure the requests and responses properly.

I decided to use an AES encryption with IvParameterSpec, SecretKeySpec and “AES/CBC/PKCS5Padding” on Java side, and  mcrypt_module “rijndael-128″ on PHP side.

Implementations

PHP

<?
class ApiCrypter
{
    private $iv  = 'fdsfds85435nfdfs'; #Same as in JAVA
    private $key = '89432hjfsd891787'; #Same as in JAVA

    public function __construct() {
    }

    public function encrypt($str) { 
	  $str = $this->pkcs5_pad($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);
    }

    public 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); 
	  $ut =  utf8_encode(trim($decrypted));
	  return $this->pkcs5_unpad($ut);
    }

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

    protected function pkcs5_pad ($text) {
	  $blocksize = 16;
	  $pad = $blocksize - (strlen($text) % $blocksize);
	  return $text . str_repeat(chr($pad), $pad);
    }

    protected function pkcs5_unpad($text) {
	  $pad = ord($text{strlen($text)-1});
	  if ($pad > strlen($text)) {
	      return false;	
	  }
	  if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) {
	      return false;
	  }
	  return substr($text, 0, -1 * $pad);
    }
}
?>

JAVA

package com.cwilldev.crypt;

import java.security.NoSuchAlgorithmException;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class ApiCrypter {

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

    public ApiCrypter()
    {
        ivspec = new IvParameterSpec(iv.getBytes());
        keyspec = new SecretKeySpec(secretkey.getBytes(), "AES");

        try {
            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }
    }

    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(text.getBytes("UTF-8"));
        }
        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;
        }
    }

}

Encryption

Java

String encryptedRequest = ApiCrypter.bytesToHex(this.apiCrypter.encrypt(jsonParams.toString()));

PHP

$encrypted = $ApiCrypter->encrypt($str);

Decryption

Java

String res = new String( this.apiCrypter.decrypt( text ), "UTF-8" );
res = URLDecoder.decode(res,"UTF-8");

PHP

$decrypted = $ApiCrypter->decrypt($str);
Posted in Android, Encryption, Java, PHP, Security, Webserver
21 comments on “Encryption between Java/Android and PHP
  1. Ahmad says:

    I have tried both versions of code (your as well as serpro’s). Both codes work great. But there is a potential issue with both the codes. If a UTF-8 string is encrypted on android then it will not decrypted by the php version correctly. For example, I encrypted “اسداسداسداسد” in android and then decrypted it using php. Instead of producing “اسداسداسداسد”, it produced “اسداسداسداسد”. Is there any way to resolve this problem?

  2. Gorance says:

    My Code bat its not work

    import android.content.Context;
    import android.telephony.TelephonyManager;

    import java.security.NoSuchAlgorithmException;

    import javax.crypto.Cipher;
    import javax.crypto.NoSuchPaddingException;
    import javax.crypto.spec.IvParameterSpec;
    import javax.crypto.spec.SecretKeySpec;

    public class MCrypt {

    Context mContext;
    TelephonyManager mtelemamanger;
    static char[] HEX_CHARS = {’0′,’1′,’2′,’3′,’4′,’5′,’6′,’7′,’8′,’9′,’a',’b',’c',’d',’e',’f'};
    public String deviceid = mtelemamanger.getSimSerialNumber();
    //public String deviceid = “894310210159403811111456457657845786485458″; ///READ THE Simcard Serialnumber and put it as deviceid
    public String iv = new String (deviceid.substring(0, 16));
    private static IvParameterSpec ivspec;
    private static SecretKeySpec keyspec;
    private static Cipher cipher;
    private String SecretKey = “0123456789abcdef”;//Dummy secretKey (CHANGE IT!)

    public MCrypt () {
    ivspec = new IvParameterSpec(iv.getBytes());

    keyspec = new SecretKeySpec(SecretKey.getBytes(), “AES”);

    try {
    cipher = Cipher.getInstance(“AES/CBC/NoPadding”);
    } catch (NoSuchAlgorithmException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    } catch (NoSuchPaddingException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }

    public MCrypt (Context context)//this is a constructor
    {
    mContext = context;
    mtelemamanger = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
    }

    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 static 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));
    //Remove trailing zeroes
    if( decrypted.length > 0)
    {
    int trim = 0;
    for( int i = decrypted.length – 1; i >= 0; i– ) if( decrypted[i] == 0 ) trim++;

    if( trim > 0 )
    {
    byte[] newArray = new byte[decrypted.length - trim];
    System.arraycopy(decrypted, 0, newArray, 0, decrypted.length – trim);
    decrypted = newArray;
    }
    }
    } catch (Exception e)
    {
    throw new Exception(“[decrypt] ” + e.getMessage());
    }
    return decrypted;
    }

    public static String bytesToHex(byte[] buf)
    {
    char[] chars = new char[2 * buf.length];
    for (int i = 0; i >> 4];
    chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
    }
    return new String(chars);
    }

    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 = 0;
    int size = 16;
    int x = source.length() % size;
    int padLength = size – x;

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

    return source;
    }
    }

  3. Gorance says:

    i need help how to get on Android Device with getSimSerialNumber(); the Simcard serial number and use it as Secret key

  4. mafrek says:

    ApiCrypter apiCrypter = new ApiCrypter();
    String encryptedRequest = null;
    try {
    encryptedRequest = ApiCrypter.bytesToHex(apiCrypter.encrypt(“Text to Encrypt”));
    } catch (Exception e) {
    e.printStackTrace();
    }
    Toast.makeText(this, encryptedRequest, Toast.LENGTH_LONG).show();

    ######################################################
    i get null?!
    help

  5. mafrek says:

    i get null string ?!

  6. Mac says:

    A big thanks to Goran for his extra code, I had the problem that he stated, added his code and everything is working again.

    Big Thanks

  7. Ajinkya says:

    This git hub code is updated?I hope!!!!is it?

  8. Mayer says:

    Hi Java folks,

    the PHP script has an critical issue. The pkcs5_unpad must return $text in case the input string is not using padding.

    The JAVA chipper does not apply a padding, when the input string is an exact multiple of the block size (this is what padding is for; it’s only used when size mismatches). The pkcs5_unpad() should return $text in this case.

    protected function pkcs5_unpad($text) {
    $pad = ord($text{strlen($text)-1});
    if ($pad > strlen($text)) {
    return $text;
    }
    if (strspn($text, chr($pad), strlen($text) – $pad) != $pad) {
    return $text;
    }
    return substr($text, 0, -1 * $pad);
    }

    Happy Coding!

  9. goran says:

    on PHP side

    in some casess unpadd function pkcs5_unpad($text) will return false and because of that function decrypt($code) will fail, return false(bool) beside decrypted data

    so i replace nex line in decrypt($code):

    return $this->pkcs5_unpad($ut);

    with:

    $unpadded = $this->pkcs5_unpad($ut);
    if($unpadded){
    return $unpadded;
    }else{
    return $ut;
    }

    and now everything work, so if you have problem in some cases(rare) try this fix…

    • cwilldev says:

      Hi Goran,

      I appreciate your input. But may you tell me what exactly “in some cases” mean?
      So I can update my code accordingly.

      Cheers

  10. Koqi says:

    This is a great posting and works /great/! Is this open-source? Thanks!

  11. Sergio Lopez says:

    While I see modifications, it couldn’t hurt you to specify that you copied/modified code for other places. Like my github repository: https://github.com/SeRPRo/Android-PHP-Encrypt-Decrypt/blob/master/Java/src/com/serpro/library/String/MCrypt.java You also left the same tags I put in the Exceptions.. :)

    Anyway.. I hope your code works well too

    • cwilldev says:

      Hello Sergio,
      I am sorry to don’t crosslink to your solution. Usually I am benefiting people I am using code of (as you can see in mostly all of my other posts). In this case I missed it – probably because I did not find all sources I have used as base. So thanks for your work and sharing!

  12. Stefan Keller says:

    47 protected function pkcs5_unpad($text) {
    PHP Quellcode
    48 $pad = ord($text{strlen($text)-1});
    49 if ($pad > strlen($text)) {
    50 return false;
    51 }

    kann mir bitte jemand erklären warum hier false zurückgeben wird ?
    müsste da nicht der $text zurückgebenen werden ?

    • cwilldev says:

      Hi Stefan,
      as Goran mentioned just now you are not the only guy facing this issue.
      Stay tuned for Goran’s reply.

    • Tom says:

      Hi Stefan,

      du hast recht. Der Code muss wie folgt lauten:

      if ($pad > strlen($text)) {
      return $text;
      }

      To the author of this website: Don’t wait for Goran’s reply. You can reproduce this issue, when passing a short encrypted string to decrypt() (on php side). Bye the way, nice work.

  13. sean says:

    It works perfectly on android and PHP server side. Thanks for your gentle sharing. Thanks a lot!

  14. Javier Garzon says:

    Gracias me sirvio bastante

  15. Joel Lazzari says:

    Works. Thanks.

Leave a Reply

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

*

Anti-spam protection

Prove that you are Human by typing the emphasized characters:


Protected by Gab Captcha 2

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>