Thursday 14 June 2018

HB Blog 156: Secure Communication For OkHttpClient Web-service API.

In cryptography, encryption is the process of encoding messages or information in such a way that only authorized parties can read it. We have seen this in my old blog HB Blog 41: Encryption And Decryption Process Of String. In that post we saw how to encrypt a string, lets see how to do similar process with web service for secure communication.
Here, we will use  java.security package for AES - 256 encryption and decryption during webservice call with OkHttpClient,
Refer the below link for complete sample code:-

Download Sample Code

Have a look on few code snippets,

//EncryptionUtility.java
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package com.harshalbenake.apiencryptionaes;

import android.util.Base64;
import android.util.Log;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

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

/**
 * Encrypt and decrypt messages using AES 256 bit encryption that are compatible with AESCrypt-ObjC and AESCrypt Ruby.
 */
public final class EncryptionUtility {
    //AESCrypt-ObjC uses CBC and PKCS7Padding
    private static final String AES_MODE = "AES/CBC/ISO10126Padding";
    private static final String CHARSET = "UTF-8";

    /**
     * Encrypt and encode message using 256-bit AES with key generated from password.
     *
     * @param key     : secrete key
     * @param message the thing you want to encrypt assumed String UTF-8
     * @return Base64 encoded CipherText
     * @throws GeneralSecurityException if problems occur during encryption
     */
    public static String encrypt(final String key, String message, String IV)
            throws GeneralSecurityException {
        try {
            byte[] ivBytes = IV.getBytes();
            byte[] keyBytes = key.getBytes("UTF-8");

            final SecretKeySpec secreteKey = new SecretKeySpec(keyBytes, "AES");
            byte[] cipherText = encrypt(secreteKey, ivBytes, message.getBytes(CHARSET));
            String encoded = Base64.encodeToString(cipherText, Base64.NO_WRAP);

            return encoded;
        } catch (UnsupportedEncodingException e) {
            throw new GeneralSecurityException(e);
        }
    }


    /**
     * More flexible AES encrypt that doesn't encode
     *
     * @param key     AES key typically 128, 192 or 256 bit
     * @param iv      Initiation Vector
     * @param message in bytes (assumed it's already been decoded)
     * @return Encrypted cipher text (not encoded)
     * @throws GeneralSecurityException if something goes wrong during encryption
     */
    public static byte[] encrypt(final SecretKeySpec key, final byte[] iv, final byte[] message)
            throws GeneralSecurityException {
        byte[] cipherText = new byte[0];
        try {
            final Cipher cipher = Cipher.getInstance(AES_MODE);
            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
            cipherText = cipher.doFinal(message);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return cipherText;
    }

    /**
     * Decrypt and decode ciphertext using 256-bit AES with key generated from password
     *
     * @param key
     * @param base64EncodedCipherText the encrpyted message encoded with base64
     * @return message in Plain text (String UTF-8)
     * @throws GeneralSecurityException if there's an issue decrypting
     */
    public static String decrypt(final String key, String base64EncodedCipherText, String IV)
            throws GeneralSecurityException {

        try {
            byte[] ivBytes = IV.getBytes();
            byte[] keyBytes = key.getBytes("UTF-8");

            final SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
            byte[] decodedCipherText = Base64.decode(base64EncodedCipherText, Base64.NO_WRAP);
            byte[] decryptedBytes = decrypt(secretKey, ivBytes, decodedCipherText);
            String message = new String(decryptedBytes, CHARSET);

            return message;
        } catch (UnsupportedEncodingException e) {
            throw new GeneralSecurityException(e);
        } catch (Exception e) {
            System.out.println(" Sys : " + e);
            throw new RuntimeException(e);
        }
    }


    /**
     * More flexible AES decrypt that doesn't encode
     *
     * @param key               AES key typically 128, 192 or 256 bit
     * @param iv                Initiation Vector
     * @param decodedCipherText in bytes (assumed it's already been decoded)
     * @return Decrypted message cipher text (not encoded)
     * @throws GeneralSecurityException if something goes wrong during encryption
     */
    public static byte[] decrypt(final SecretKeySpec key, final byte[] iv, final byte[] decodedCipherText)
            throws GeneralSecurityException {
        final Cipher cipher = Cipher.getInstance(AES_MODE);
        IvParameterSpec ivSpec = new IvParameterSpec(iv);
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
        byte[] decryptedBytes = cipher.doFinal(decodedCipherText);
        return decryptedBytes;
    }
}

//OKHTTPService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package com.harshalbenake.apiencryptionaes;

import android.util.ArrayMap;
import android.util.Log;

import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;

/**
 * This class is used fto get and post service response to server using okhttp.
 */
public class OKHTTPService {
    public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
    public static final String mStrUrl="Url";
    public static final String mStrRequestJson="RequestJson";
    public static final String mStrRequestEncoded="RequestEncoded";
    public static final String mStrResponseEncoded="ResponseEncoded";
    public static final String mStrResponseJson="ResponseJson";


    public OKHTTPService() {
    }

    /**
     * gets Ok Http Service
     *
     * @param strUrl
     * @param strJson
     * @return
     */
    public static HashMap<String,String> requestACallToServer(String strUrl, String strJson) {
        HashMap<String,String> hashMap=new HashMap<String, String>();
        try {
            System.out.println("OkHttp get Request: " + strJson);
            String strEncodedRequest = EncryptionUtility.encrypt("YourKey",strJson, "YourKey");            System.out.println("OkHttp get EncodedRequest: " + strEncodedRequest);
            RequestBody body = RequestBody.create(JSON, strEncodedRequest);
            Request request = new Request.Builder()
                    .url(strUrl)
                    .post(body)
                    .build();
            OkHttpClient.Builder builder = new OkHttpClient.Builder().protocols(Arrays.asList(Protocol.HTTP_1_1));
            builder.connectTimeout(3, TimeUnit.MINUTES)
                    .writeTimeout(3, TimeUnit.MINUTES)
                    .readTimeout(3, TimeUnit.MINUTES);
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                @Override
                public void log(String message) {
                    Log.d("HttpLogging", message);
                }
            });
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .protocols(Arrays.asList(Protocol.HTTP_1_1))
                    .addInterceptor(logging)
                    .connectTimeout(3, TimeUnit.MINUTES)
                    .writeTimeout(3, TimeUnit.MINUTES)
                    .readTimeout(3, TimeUnit.MINUTES)
                    .build();
            Response response = okHttpClient.newCall(request).execute();
            String strEncodedResponse = response.body().string();
            System.out.println("OkHttp get EncodedResponse: " + strEncodedResponse);
            String strResponse = EncryptionUtility.decrypt("YourKey", strEncodedResponse, "YourKey");            System.out.println("OkHttp get Response: " + strResponse);
            hashMap.put(mStrUrl,strUrl);
            hashMap.put(mStrRequestJson,strJson);
            hashMap.put(mStrRequestEncoded,strEncodedRequest);
            hashMap.put(mStrResponseEncoded,strEncodedResponse);
            hashMap.put(mStrResponseJson,strResponse.replaceAll("\\r\\n", ""));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return hashMap;
    }
}

//DataService.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.harshalbenake.apiencryptionaes;

import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;

import java.util.HashMap;

/**
 * Data Service AsyncTask
 */
public class DataService extends AsyncTask<String, String, HashMap<String, String>> {
    MainActivity mContext;
    private ProgressDialog mProgressDialog;

    public DataService(MainActivity context) {
            this.mContext = context;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        if (NetworkUtility.isOnline(mContext)) {
            mProgressDialog = new ProgressDialog(mContext);
            mProgressDialog.setMessage("Loading...");
            mProgressDialog.setCancelable(false);
            mProgressDialog.setCanceledOnTouchOutside(false);
            mProgressDialog.show();
        } else {
            Toast.makeText(mContext, "Not connected to Internet", Toast.LENGTH_SHORT).show();
            cancel(true);
        }
    }

    @Override
    protected HashMap<String, String> doInBackground(String... params) {
        HashMap<String, String> hashMap=null;
        if (!isCancelled()) {
            try {
                String strUrl ="xxxURLxxx";
                String strJsonData="xxxJsonDataxxx";
                 hashMap = OKHTTPService.requestACallToServer(strUrl,strJsonData.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return hashMap;
    }

    @Override
    protected void onPostExecute(HashMap<String, String> resultMap) {
        super.onPostExecute(resultMap);
        try {
            if (mProgressDialog != null && mProgressDialog.isShowing() == true) {
                mProgressDialog.dismiss();
            }
            if(resultMap!=null && resultMap.size()>0){
                mContext.mtv_url.setText(OKHTTPService.mStrUrl+": "+resultMap.get(OKHTTPService.mStrUrl)+"\n");
                mContext.mtv_requestjson.setText(OKHTTPService.mStrRequestJson+": "+resultMap.get(OKHTTPService.mStrRequestJson)+"\n");
                mContext.mtv_requestencoded.setText(OKHTTPService.mStrRequestEncoded+": "+resultMap.get(OKHTTPService.mStrRequestEncoded)+"\n");
                mContext.mtv_responsencoded.setText(OKHTTPService.mStrResponseEncoded+": "+resultMap.get(OKHTTPService.mStrResponseEncoded)+"\n");
                mContext.mtv_responsejson.setText(OKHTTPService.mStrResponseJson+": "+resultMap.get(OKHTTPService.mStrResponseJson)+"\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}