In this lab, we will learn to create an RSA Encryption application through Android Studio IDE. Users can enter a text into the editable text box and then click the "Encrypt" or "Decrypt" button. The application will put the ciphertext onto the second textbox. Then, clicking the "Decrypt" button will result in the plain text shown in the last textbox.
Objective
This lab gives the reader a first impression of how the encryption and decryption work by taking RSA encryption and decryption algorithm as an example. Also, we expect the reader to gain a general notion of the public-key encryption infrastructure. In this lab, we will utilize the public-key infrastructure support by Java. For the private-key encryption infrastructure example AES, we will leave it as a post-lab assignment for readers' implementation.
You should never attempt to penetrate a particular system or adversely affect its operation. Such actions are a direct violation of KSU policy and, in some cases, violations of State and Federal law. Likewise, you should refrain from writing computer viruses, worms, self-reproducing code, or any other kind of potentially damaging software for this course unless you have explicit, written approval for the specific type of software that you wish to create. These kinds of programs are notoriously difficult to control and their release (intentional or otherwise) can result in substantial civil and criminal penalties. Please read and review the ACM Code of Ethics and Professional Conduct.
Environment
Android Studio 3.1.4
In order to build the apk and run tests, you must have the JCE (Java Cryptogrpahy Extension) Unlimited Strength policy jars installed for your JRE runtime.
JCE download for Java 8: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html
After downloading the file, please read the "README.txt" file to figure out where to place the .JAR files. If you do not do this then you will see the error:
java.lang.RuntimeException: java.security.InvalidKeyException: Illegal key size or default parameters
Create a new Android Studio project and name it “AndroidEncryptionExample” with company domain of "android.encrypt.com" and click on Next.
Choose the form factor that your app will run on. Make sure that the API you are using is at least API 22: Android 5.1 (Lollipop), and click on Next.
Select the Empty Activity and click on Next.
Set the Activity name as EncryptionActivity and click on Finish.
You should see something similar to the following image.
Copy and paste the following code into “EncryptionActivity.java”. Notice that the package name may different.
EncryptionActivity.java
package com.example.androidencryptionexample;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import org.ow2.util.base64.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.util.Arrays;
public class EncryptionActivity extends Activity {
private Button encryptButton = null, decryptButton = null, clearButton = null;
private EditText decryptedText = null, encryptedText = null, inputtedUnencryptedText = null ;
//RSA key pair (public and private)
private KeyPair rsaKey = null;
//encrypted aes key and ivs combined
private byte[] encryptedAESKey = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_encryption);
wireUI();
// specify spongyCastle for android runtime provider
AESEncryptDecrypt.setProvider(new org.spongycastle.jce.provider.BouncyCastleProvider(), "SC");
this.rsaKey = RSAEncryptDecrypt.generateRSAKey();
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.encryption, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if (id == R.id.action_settings)
{
return true;
}
return super.onOptionsItemSelected(item);
}
/*
* wire the ui
*/
private void wireUI()
{
this.inputtedUnencryptedText = (EditText)findViewById(R.id.inputtedUnencryptedText);
this.encryptedText = (EditText)findViewById(R.id.encryptedText);
this.decryptedText = (EditText)findViewById(R.id.decryptedText);
this.encryptButton = (Button)findViewById(R.id.encryptButton);
this.encryptButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
encryptButton();
}
});
this.decryptButton = (Button)findViewById(R.id.decryptButton);
this.decryptButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
decryptButton();
}
});
this.clearButton = (Button)findViewById(R.id.clearButton);
this.clearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
clearButton();
}
});
}
private void decryptButton()
{
String encText = this.encryptedText.getText().toString();
//sanity test on input from ui
if (encText != null && encText.trim().length() > 0)
{
//decrypt the stored aes and ivs key
byte[] decryptedAESKeyIVS = RSAEncryptDecrypt.decryptRSA(this.encryptedAESKey, this.rsaKey.getPrivate());
//we combined the aes key and iv earlier in encryptButton() now after we decrypted
//the value we split it up
byte[] aesKey = Arrays.copyOfRange(decryptedAESKeyIVS, 0, 32);
byte[] ivs = Arrays.copyOfRange(decryptedAESKeyIVS, 32, 48);
char[] aesKeyChar = null;
try
{
//convert the binary aes key to a char array
aesKeyChar = new String(aesKey, "UTF-8").toCharArray();
} catch (UnsupportedEncodingException e)
{
Log.e(EncryptionActivity.class.getName(), e.getMessage(), e);
return;
}
//set up your streams for decryption
ByteArrayInputStream encInputStream = new ByteArrayInputStream(Base64.decode(encText.toCharArray()));
ByteArrayOutputStream plainTextOutputStream = new ByteArrayOutputStream(1024 * 10);
String unencryptedString = "";
//main aes decrypt function
AESEncryptDecrypt.aesDecrypt(encInputStream,
aesKeyChar,
ivs,
AESEncryptDecrypt.AESCipherType.AES_CBC_PKCS5PADDING,
plainTextOutputStream);
try
{
//convert decrypted outputstream to a string
unencryptedString = new String(plainTextOutputStream.toByteArray(),"UTF-8");
} catch (UnsupportedEncodingException e)
{
Log.e(EncryptionActivity.class.getName(), e.getMessage(), e);
return;
}
//set decrypted text to the ui
this.decryptedText.setText(unencryptedString);
}
}
private void clearButton()
{
this.inputtedUnencryptedText.setText(getString(R.string.default_hint));
this.encryptedText.setText(" ");
this.decryptedText.setText(" ");
this.encryptedAESKey = null;
}
private void encryptButton()
{
final String inputtedUnencryptedText = this.inputtedUnencryptedText.getText().toString();
//sanity check on input
if (TextUtils.isEmpty(inputtedUnencryptedText))
{
return;
}
new AsyncTask<String, Integer, String>(){
@Override
protected String doInBackground(String... params)
{
return encryptString(inputtedUnencryptedText);
}
@Override
protected void onPostExecute(String encryptedString)
{
super.onPostExecute(encryptedString);
if (encryptedString == null) return;
encryptedText.setText(encryptedString);
}
}.execute();
}
@Nullable
private String encryptString(String inputtedUnencryptedText)
{
ByteArrayInputStream plainTextInputStream;
try
{
//create an inputstream from a string
plainTextInputStream = new ByteArrayInputStream(inputtedUnencryptedText.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e)
{
Log.e(EncryptionActivity.class.getName(), e.getMessage(), e);
return null;
}
ByteArrayOutputStream encOutputStream = new ByteArrayOutputStream(1024 * 10);
//main aes encrypt
byte[] iv = AESEncryptDecrypt.aesEncrypt(plainTextInputStream,
AESEncryptDecrypt.NOT_SECRET_ENCRYPTION_KEY.toCharArray(),
AESEncryptDecrypt.AESCipherType.AES_CBC_PKCS5PADDING,
encOutputStream);
//combine the aes key and iv
byte[] combined = Util.concat(AESEncryptDecrypt.NOT_SECRET_ENCRYPTION_KEY.getBytes(),
iv);
//encrypt the combined keys using rsa and store the encrypted value
encryptedAESKey = RSAEncryptDecrypt.encryptRSA(combined, this.rsaKey.getPublic());
//set ui textview to encrypted base64 encoded value
String encryptedString = new String(Base64.encode(encOutputStream.toByteArray()));
return encryptedString;
}
}
Right click on com.example.androidencryptionexample in the project side-bar on the left and select New->Java Class.
Name your new java class “AESEncryptDecrypt” and click “OK”.
Copy the following code into the newly created “AESEncryptDecrypt.java”.
AESEncryptDecrypt.java
package com.example.androidencryptionexample;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import org.ow2.util.base64.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.util.Arrays;
public class EncryptionActivity extends Activity {
private Button encryptButton = null, decryptButton = null, clearButton = null;
private EditText decryptedText = null, encryptedText = null, inputtedUnencryptedText = null ;
//RSA key pair (public and private)
private KeyPair rsaKey = null;
//encrypted aes key and ivs combined
private byte[] encryptedAESKey = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_encryption);
wireUI();
// specify spongyCastle for android runtime provider
AESEncryptDecrypt.setProvider(new org.spongycastle.jce.provider.BouncyCastleProvider(), "SC");
this.rsaKey = RSAEncryptDecrypt.generateRSAKey();
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
getMenuInflater().inflate(R.menu.encryption, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
int id = item.getItemId();
if (id == R.id.action_settings)
{
return true;
}
return super.onOptionsItemSelected(item);
}
/*
* wire the ui
*/
private void wireUI()
{
this.inputtedUnencryptedText = (EditText)findViewById(R.id.inputtedUnencryptedText);
this.encryptedText = (EditText)findViewById(R.id.encryptedText);
this.decryptedText = (EditText)findViewById(R.id.decryptedText);
this.encryptButton = (Button)findViewById(R.id.encryptButton);
this.encryptButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
encryptButton();
}
});
this.decryptButton = (Button)findViewById(R.id.decryptButton);
this.decryptButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
decryptButton();
}
});
this.clearButton = (Button)findViewById(R.id.clearButton);
this.clearButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
clearButton();
}
});
}
private void decryptButton()
{
String encText = this.encryptedText.getText().toString();
//sanity test on input from ui
if (encText != null && encText.trim().length() > 0)
{
//decrypt the stored aes and ivs key
byte[] decryptedAESKeyIVS = RSAEncryptDecrypt.decryptRSA(this.encryptedAESKey, this.rsaKey.getPrivate());
//we combined the aes key and iv earlier in encryptButton() now after we decrypted
//the value we split it up
byte[] aesKey = Arrays.copyOfRange(decryptedAESKeyIVS, 0, 32);
byte[] ivs = Arrays.copyOfRange(decryptedAESKeyIVS, 32, 48);
char[] aesKeyChar = null;
try
{
//convert the binary aes key to a char array
aesKeyChar = new String(aesKey, "UTF-8").toCharArray();
} catch (UnsupportedEncodingException e)
{
Log.e(EncryptionActivity.class.getName(), e.getMessage(), e);
return;
}
//set up your streams for decryption
ByteArrayInputStream encInputStream = new ByteArrayInputStream(Base64.decode(encText.toCharArray()));
ByteArrayOutputStream plainTextOutputStream = new ByteArrayOutputStream(1024 * 10);
String unencryptedString = "";
//main aes decrypt function
AESEncryptDecrypt.aesDecrypt(encInputStream,
aesKeyChar,
ivs,
AESEncryptDecrypt.AESCipherType.AES_CBC_PKCS5PADDING,
plainTextOutputStream);
try
{
//convert decrypted outputstream to a string
unencryptedString = new String(plainTextOutputStream.toByteArray(),"UTF-8");
} catch (UnsupportedEncodingException e)
{
Log.e(EncryptionActivity.class.getName(), e.getMessage(), e);
return;
}
//set decrypted text to the ui
this.decryptedText.setText(unencryptedString);
}
}
private void clearButton()
{
this.inputtedUnencryptedText.setText(getString(R.string.default_hint));
this.encryptedText.setText(" ");
this.decryptedText.setText(" ");
this.encryptedAESKey = null;
}
private void encryptButton()
{
final String inputtedUnencryptedText = this.inputtedUnencryptedText.getText().toString();
//sanity check on input
if (TextUtils.isEmpty(inputtedUnencryptedText))
{
return;
}
new AsyncTask<String, Integer, String>(){
@Override
protected String doInBackground(String... params)
{
return encryptString(inputtedUnencryptedText);
}
@Override
protected void onPostExecute(String encryptedString)
{
super.onPostExecute(encryptedString);
if (encryptedString == null) return;
encryptedText.setText(encryptedString);
}
}.execute();
}
@Nullable
private String encryptString(String inputtedUnencryptedText)
{
ByteArrayInputStream plainTextInputStream;
try
{
//create an inputstream from a string
plainTextInputStream = new ByteArrayInputStream(inputtedUnencryptedText.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e)
{
Log.e(EncryptionActivity.class.getName(), e.getMessage(), e);
return null;
}
ByteArrayOutputStream encOutputStream = new ByteArrayOutputStream(1024 * 10);
//main aes encrypt
byte[] iv = AESEncryptDecrypt.aesEncrypt(plainTextInputStream,
AESEncryptDecrypt.NOT_SECRET_ENCRYPTION_KEY.toCharArray(),
AESEncryptDecrypt.AESCipherType.AES_CBC_PKCS5PADDING,
encOutputStream);
//combine the aes key and iv
byte[] combined = Util.concat(AESEncryptDecrypt.NOT_SECRET_ENCRYPTION_KEY.getBytes(),
iv);
//encrypt the combined keys using rsa and store the encrypted value
encryptedAESKey = RSAEncryptDecrypt.encryptRSA(combined, this.rsaKey.getPublic());
//set ui textview to encrypted base64 encoded value
String encryptedString = new String(Base64.encode(encOutputStream.toByteArray()));
return encryptedString;
}
}
Create 'RSAEncryptDecrypt.java' and 'Util.java' as well then copy and paste them respectively.
RSAEncryptDecrypt.java
package com.example.androidencryptionexample;
import android.util.Log;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import javax.crypto.Cipher;
/**
* RSA Helper Encryption Class
*/
public class RSAEncryptDecrypt {
//key length
public static final int KEY_LENGTH = 2048;
//main family of rsa
public static final String RSA = "RSA";
/**
* generate a 2048 bit RSA key
*
* @return a 2048 bit rsa key
*/
public static KeyPair generateRSAKey()
{
KeyPairGenerator kpg = null;
try
{
//get an RSA key generator
kpg = KeyPairGenerator.getInstance(RSA);
}
catch (NoSuchAlgorithmException e)
{
Log.e(RSAEncryptDecrypt.class.getName(), e.getMessage(), e);
throw new RuntimeException(e);
}
//initialize the key to 2048 bits
kpg.initialize(KEY_LENGTH);
//return the generated key pair
return kpg.genKeyPair();
}
/**
* main RSA encrypt method
*
* @param plain plain text you want to encrypt
* @param publicKey public key to encrypt with
* @return encrypted text
*/
public static byte[] encryptRSA(byte[] plain, PublicKey publicKey)
{
byte[] enc = null;
try
{
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
enc = cipher.doFinal(plain);
}
//no need to catch 4 different exceptions
catch (Exception e)
{
Log.e(RSAEncryptDecrypt.class.getName(), e.getMessage(), e);
throw new RuntimeException(e);
}
return enc;
}
/**
* main RSA decrypt method
*
* @param enc encrypted text you want to dcrypt
* @param privateKey private key to use for decryption
* @return plain text
*/
public static byte[] decryptRSA(byte[] enc, PrivateKey privateKey)
{
byte[] plain = null;
try
{
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
plain = cipher.doFinal(enc);
}
//no need to catch 4 different exceptions
catch (Exception e)
{
Log.e(RSAEncryptDecrypt.class.getName(), e.getMessage(), e);
throw new RuntimeException(e);
}
return plain;
}
}
Util.java
Util.java
package com.example.androidencryptionexample;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class Util
{
//helper function that concats two byte arrays
public static byte[] concat(byte[] first, byte[] second){
byte[] combined = new byte[first.length + second.length];
System.arraycopy(first, 0, combined, 0, first.length);
System.arraycopy(second, 0, combined, first.length, second.length);
return combined;
}
//##########################
// BELOW IS STRAIGHT COPIED FROM IOUtils.copy(inputstream, outputstream)
// was receiving errors during instrumentation test using apache
// org.apache.commons:commons-io:1.3.2 :(
/**
* The default buffer size to use.
*/
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
// copy from InputStream
//-----------------------------------------------------------------------
/**
* Copy bytes from an <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
* <p>
* Large streams (over 2GB) will return a bytes copied value of
* <code>-1</code> after the copy has completed since the correct
* number of bytes cannot be returned as an int. For large streams
* use the <code>copyLarge(InputStream, OutputStream)</code> method.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> to write to
* @return the number of bytes copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @throws ArithmeticException if the byte count is too large
* @since Commons IO 1.1
*/
public static int copy(InputStream input, OutputStream output) throws IOException {
long count = copyLarge(input, output);
if (count > Integer.MAX_VALUE) {
return -1;
}
return (int) count;
}
/**
* Copy bytes from a large (over 2GB) <code>InputStream</code> to an
* <code>OutputStream</code>.
* <p>
* This method buffers the input internally, so there is no need to use a
* <code>BufferedInputStream</code>.
*
* @param input the <code>InputStream</code> to read from
* @param output the <code>OutputStream</code> to write to
* @return the number of bytes copied
* @throws NullPointerException if the input or output is null
* @throws IOException if an I/O error occurs
* @since Commons IO 1.3
*/
public static long copyLarge(InputStream input, OutputStream output)
throws IOException
{
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
long count = 0;
int n = 0;
while (-1 != (n = input.read(buffer))) {
output.write(buffer, 0, n);
count += n;
}
return count;
}
}
Copy the following code into “activity_encryption.xml”.
activity_encryption.xml
<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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context="com.example.androidencryptionexample.EncryptionActivity">
<EditText
android:id="@+id/inputtedUnencryptedText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="15dip"
android:layout_marginTop="15dip"
android:minLines="3"
android:gravity="top|left"
android:padding="5dip"
android:text="@string/default_hint"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Encrypt"
android:padding="10dip"
android:id="@+id/encryptButton"
android:layout_below="@id/inputtedUnencryptedText"
android:layout_marginTop="15dip"
android:layout_alignParentLeft="true"/>
<EditText
android:id="@+id/encryptedText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/encryptButton"
android:layout_below="@id/inputtedUnencryptedText"
android:layout_marginLeft="15dip"
android:layout_marginTop="15dip"
android:minLines="5"
android:gravity="top|left"
android:padding="5dip"
android:editable="false"
android:ellipsize="end"
/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Decrypt"
android:padding="10dip"
android:id="@+id/decryptButton"
android:layout_below="@id/encryptedText"
android:layout_marginTop="15dip"
android:layout_alignParentLeft="true"/>
<EditText
android:id="@+id/decryptedText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/encryptButton"
android:layout_below="@id/encryptedText"
android:layout_marginLeft="15dip"
android:layout_marginTop="15dip"
android:minLines="3"
android:gravity="top|left"
android:padding="5dip"
android:editable="false"/>
<Button
android:id="@+id/clearButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/decryptedText"
android:layout_centerHorizontal="true"
android:layout_marginTop="30dip"
android:text="Clear"
android:padding="10dip"/>
</RelativeLayout>
Copy the following code into “strings.xml”.
strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">EncryptionExample</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
<string name="default_hint">I\'m not typing anything in here!</string>
</resources>
Right click on values folder, create a new xml file and name it as dimens.xml
Copy the following code into “dimens.xml”
dimens.xml
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
Save your project and run it on the AVD.
When the user click on Encrypt button and Decrypt button, the application will display the respective information for encryption
and decryption.