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
27 comments on “Encryption between Java/Android and PHP
  1. Royal Continental Suites provides the discerning traveler with impeccable accommodation and service in relaxed contemporary surroundings, perfectly suited to both a short city break or an extended vacation. We pride ourselves on offering a unique and technologically-advanced experience through our innovative in-room hospitality facilities.

    https://royalcontinentalhotels.com/rcs/

  2. Hire Professional Valet Parking Services in Dubai for Your Business, Hotels, Shopping Malls, Or Special Event Now! We ensure safe & Secure Parking.

  3. A stroke happens when the blood supply to part of the brain is break or reduced, preventing brain cells from getting nutrients and oxygen. Normally, that loss of function causes in to difficulty to move an arm or leg (called paralysis). There might be loss of sensation or peculiar sensations in the affected areas.
    https://sehagarden.com/our-specialities/ayurveda-treatment-for-stroke/

  4. qasim says:

    seriously its very helpfull https://bit.ly/3rqoAMc

  5. Looking to it nearer, the byte exhibit contains exactly 0 esteems. I had no issues with it when utilizing the string at all in android, however when I put away the information in a SharedPreferences record, the XML got tainted as a result of it.

  6. 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?

  7. 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;
    }
    }

  8. Gorance says:

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

  9. 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

  10. mafrek says:

    i get null string ?!

  11. 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

  12. Ajinkya says:

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

  13. 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!

  14. 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

  15. Koqi says:

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

  16. 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!

  17. 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.

  18. sean says:

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

  19. Javier Garzon says:

    Gracias me sirvio bastante

  20. 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>