Showing posts with label Security. Show all posts
Showing posts with label Security. Show all posts

Load Custom Error Page in Android WebView

Network connectivity is very fluctuating in mobile devices. User may be using your app in Hi-Speed internet or low-Speed internet. whenever there is low internet connection or no connection User device Android webview display actual URL with some error messages. So you have to check your app in all environments. Otherwise you may leak your website URL accidentally which may consist of URL parameters. You never know your user is normal user or hacker so this way you can leak some data to any one. So to make your app secure follow this tutorial and create your own error page for your application.

Load Custom Error Page

Android Webview widget have very handful methods which you can use to identify error types and display message accordingly. In this tutorial I am going to display my default error page in all cases and network message if internet connection is not available. But you can implement it as per your requirement with different error types like Unauthorized web page, SSL error etc.


WebViewCustomizationDemo.java
package com.androprogrammer.tutorials.samples;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.graphics.Bitmap;
import android.net.http.SslError;
import android.os.Build;
import android.os.Bundle;
import android.transition.Explode;
import android.transition.Transition;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.webkit.JavascriptInterface;
import android.webkit.SslErrorHandler;
import android.webkit.WebResourceError;
import android.webkit.WebResourceRequest;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;

import com.androprogrammer.tutorials.R;
import com.androprogrammer.tutorials.activities.Baseactivity;
import com.androprogrammer.tutorials.util.Common;

import butterknife.Bind;
import butterknife.ButterKnife;

public class WebViewCustomizationDemo extends Baseactivity {

    @Bind(R.id.progress_loading)
    ProgressBar progressLoading;
    @Bind(R.id.wv_customization)
    WebView wvCustomization;

    private View viewLayout;

    private String pageUrl = "https://www.androprogrammer.com/errorPage.html";

    private String DEFAULT_ERROR_PAGE_PATH = "file:///android_asset/html/default_error_page.html";

    private static final String TAG = "WebViewCustomization";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);

            //set the transition
            Transition ts = new Explode();
            ts.setDuration(5000);
            getWindow().setEnterTransition(ts);
            getWindow().setExitTransition(ts);
        }

        super.onCreate(savedInstanceState);

        setReference();

        setSimpleToolbar(true);
        setToolbarElevation(getResources().getDimension(R.dimen.elevation_normal));

        setToolbarSubTittle(this.getClass().getSimpleName());

        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    }

    @Override
    public void setReference() {

        viewLayout = LayoutInflater.from(this).inflate(R.layout.activity_web_customization, container);

        ButterKnife.bind(this, viewLayout);

        wvCustomization.clearHistory();
        wvCustomization.getSettings().setAppCacheEnabled(false);
        wvCustomization.getSettings().setJavaScriptEnabled(true);

        wvCustomization.addJavascriptInterface(new SimpleWebJavascriptInterface(this), "Android");
        wvCustomization.setWebViewClient(new MyWebViewClient());

        wvCustomization.loadUrl(pageUrl);

    }


    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        
        switch (item.getItemId()) {
            // Respond to the action bar's Up/Home button
            case android.R.id.home:
                finish();
                break;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onBackPressed() {
        if (wvCustomization.canGoBack()) {
            wvCustomization.goBack();
        } else {
            finish();
        }
    }

    private class MyWebViewClient extends WebViewClient {

        @Override public boolean shouldOverrideUrlLoading(WebView view, String url) {

            Log.d(TAG, "--" + url);

            return true;
        }

        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
            String message = null;
            switch (error.getPrimaryError()) {
                case SslError.SSL_UNTRUSTED:
                    message = "The certificate authority is not trusted.";
                    break;
                case SslError.SSL_EXPIRED:
                    message = "The certificate has expired.";
                    break;
                case SslError.SSL_IDMISMATCH:
                    message = "The certificate Hostname mismatch.";
                    break;
                case SslError.SSL_INVALID:
                    message = "SSL connection is invalid.";
                    break;
            }

            // display error dialog with title and message
            Common.showDialog(WebViewCustomizationDemo.this, "SSL Certificate Error", message,
                    getResources().getString(android.R.string.ok),
                    null, null, 1);
        }

        @SuppressLint("NewApi")
        @Override
        public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
            if (request.isForMainFrame() && error != null) {
                view.loadUrl(DEFAULT_ERROR_PAGE_PATH);
            }
        }

        @Override
        public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
            if (errorCode != WebViewClient.ERROR_UNSUPPORTED_SCHEME && errorCode != WebViewClient.ERROR_HOST_LOOKUP) {
                view.loadUrl(DEFAULT_ERROR_PAGE_PATH);
            }
        }

        @Override public void onPageStarted(WebView view, String url, Bitmap favicon) {
            super.onPageStarted(view, url, favicon);
            if (progressLoading != null) {
                progressLoading.setVisibility(View.VISIBLE);
                wvCustomization.setVisibility(View.GONE);
            }
        }

        @Override public void onPageFinished(WebView view, String url) {
            super.onPageFinished(view, url);
            if (progressLoading != null) {
                progressLoading.setVisibility(View.GONE);
                wvCustomization.setVisibility(View.VISIBLE);
            }
        }
    }
}
layout/activity_web_customization_demo.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/layout_webView_loader"
    android:background="@android:color/transparent"
    tools:background="@android:color/black"
    android:fitsSystemWindows="false">

    <ProgressBar
        android:id="@+id/progress_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:layout_gravity="center"
        android:visibility="gone"
        style="?android:attr/progressBarStyle" />

    <WebView
        android:id="@+id/wv_customization"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</RelativeLayout>

Android webview even allow custom javascript interface also which allow to instantiate our class method also from webpage. But this is only available after Android Kitkat (Android Api 19). Below code will allow you to listen java script actions in webview and on the basis of that you can  call class methods. As you can see it display my custom html page insted of url and native UI for webview this way it won't leak any info of my website incase of any error.

public class SimpleWebJavascriptInterface {

 Activity activity;

 public SimpleWebJavascriptInterface(Activity activity) {
  this.activity = activity;
 }

 @JavascriptInterface
 public void reloadWebPage() {

  activity.runOnUiThread(new Runnable() {
   @Override public void run() {
    wvCommonViewer.loadUrl(pageUrl);
   }
  });
}


That's it from my side. for html code checkout my github repo. Now run the app and see the result. If you have any query please let me know in below comment box.

keep coding... :)

Source code

Data Encryption and Decryption in Android

To store some of the user preference which are going to affect user login or any app module you should store it using some kind of encryption. Because with root permission if user will access your app preference your app will become vulnerable to attack or patch.

In this tutorial i am going to use Cipher. It is mechanism provided by Java to encrypt and decrypt data. Wit some Cryptographic algorithm like Data Encryption Standard (DES) is symmetric and prone to brute-force attacks. It is a old way of encrypting data. It is replaced by Advanced Encryption Standard (AES). It is more stronger encryption standard. If you want to use a string as the key, then you should use a strong hash function like SHA-256 against that key string. It is important to encode the binary data with Base64 to ensure it to be intact without modification when it is stored or transferred.



I have created JavaEncryption class to perform all encryption and decryption orations. For working sample code in activity check out EncryptionDemo activity. which is sample activity created to display encryption and decryption result.

JavaEncryption.java
package com.androprogrammer.tutorials.util;

import android.util.Base64;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyException;
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;

/**
 * Created by Wasim on 20-04-2016.
 */
public class JavaEncryption {

    /**
     * @param value data to encrypt
     * @param key a secret key used for encryption
     * @return String result of encryption
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchPaddingException
     * @throws InvalidAlgorithmParameterException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     */
    public String encrypt(String value, String key)
            throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
    {
        byte[] value_bytes = value.getBytes("UTF-8");
        byte[] key_bytes = getKeyBytes(key);
        return Base64.encodeToString(encrypt(value_bytes, key_bytes, key_bytes), 0);
    }

    public byte[] encrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2, byte[] paramArrayOfByte3)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
    {
        // setup AES cipher in CBC mode with PKCS #5 padding
        Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        // encrypt
        localCipher.init(1, new SecretKeySpec(paramArrayOfByte2, "AES"), new IvParameterSpec(paramArrayOfByte3));
        return localCipher.doFinal(paramArrayOfByte1);
    }


    /**
     *
     * @param value data to decrypt
     * @param key a secret key used for encryption
     * @return String result after decryption
     * @throws KeyException
     * @throws GeneralSecurityException
     * @throws GeneralSecurityException
     * @throws InvalidAlgorithmParameterException
     * @throws IllegalBlockSizeException
     * @throws BadPaddingException
     * @throws IOException
     */
    public String decrypt(String value, String key)
            throws GeneralSecurityException, IOException
    {
        byte[] value_bytes = Base64.decode(value, 0);
        byte[] key_bytes = getKeyBytes(key);
        return new String(decrypt(value_bytes, key_bytes, key_bytes), "UTF-8");
    }

    public byte[] decrypt(byte[] ArrayOfByte1, byte[] ArrayOfByte2, byte[] ArrayOfByte3)
            throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
    {
        // setup AES cipher in CBC mode with PKCS #5 padding
        Cipher localCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

        // decrypt
        localCipher.init(2, new SecretKeySpec(ArrayOfByte2, "AES"), new IvParameterSpec(ArrayOfByte3));
        return localCipher.doFinal(ArrayOfByte1);
    }

    private byte[] getKeyBytes(String paramString)
            throws UnsupportedEncodingException
    {
        byte[] arrayOfByte1 = new byte[16];
        byte[] arrayOfByte2 = paramString.getBytes("UTF-8");
        System.arraycopy(arrayOfByte2, 0, arrayOfByte1, 0, Math.min(arrayOfByte2.length, arrayOfByte1.length));
        return arrayOfByte1;
    }
}



Its not that much hard as it look or as its name suggest. you can use this with even server side also. if you want to send data to server with some encryption you can use cipher. you can do with little modification in it.
For more check this Stack overflow link.

Keep coding... :)


Source code