view-Controller-model
In the previous lessons we created the layout and connected the events and event handlers. Now we are going to create yet another class to encapsulate the application algorithms. This is the model class in view-Controller-model. I capitalize the C since the Controller owns the view and the model. This is not the MVC of Smalltalk. The view is defined in main.xml. The event handling code (Controller) is defined in ConfusedText.java. The purpose of the algorithms in the model.java class is to encrypt and decrypt text using javax.crytpo. First we will create a new file Model.java. Go to File --> New --> Class. Enter the following and click on Finish to create the Model.java file:
Source Folder: confusetext.src
Package: com.google.sites.site.jalcomputing.android.app
Name: Model
Stub Code
Enter the stub code in our Model class:
package com.google.sites.site.jalcomputing.android.app;
public class Model {
private String key;
public String encrypt(String inString, String key){
return("Encrypted, "+key);
}
public String decrypt(String inString, String key){
return("Decrypted, "+key);
}
}
Figure 1. Model class with stub code
Now in our ConfuseText class we create an instance of the Model class and call the stub methods.
package com.google.sites.site.jalcomputing.android.app;
import android.app.Activity;
import android.os.Bundle;
import android.widget.EditText;
import android.view.View;
import android.widget.Button;
public class ConfuseText extends Activity{
private EditText editTextConfusedText;
private EditText editTextPlainText;
private EditText editTextPassword;
private Button buttonConfuseText;
private Button buttonPlainText;
private Model model= new Model();
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// initialize event listeners using anonymous inner classes
buttonConfuseText= (Button)findViewById(R.id.ButtonConfuseText);
editTextConfusedText= (EditText)findViewById(R.id.EditTextConfusedText);
editTextPlainText= (EditText)findViewById(R.id.EditTextPlainText);
editTextPassword= (EditText)findViewById(R.id.EditTextPassword);
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String key, inString, outString;
key= editTextPassword.getText().toString();
inString= editTextPlainText.getText().toString();
outString= model.encrypt(inString, key);
editTextConfusedText.setText(outString);
}
});
buttonPlainText= (Button)findViewById(R.id.ButtonPlainText);
editTextPlainText= (EditText)findViewById(R.id.EditTextPlainText);
buttonPlainText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String key, inString,outString;
key= editTextPassword.getText().toString();
inString= editTextConfusedText.getText().toString();
outString= model.decrypt(inString, key);
editTextPlainText.setText(outString);
}
});
}
}
Figure 2. ConfuseText code calling stub code
Here is our app using the stub code after clicking on both buttons.
Figure 3. App in action with stub code
Using DES
Now the fun begins. Here is our app using DES encryption!
Figure 4. App in action using 8 byte key and DES encryption
The following code expands the stub code above.
package com.google.sites.site.jalcomputing.android.app;
import javax.crypto.*;
import java.security.spec.*;
import javax.crypto.spec.*;
import android.util.Base64;
import java.io.*;
import java.security.*;
public class Model {
public String encrypt(String inString, String key){
String outString="Encrypted";
try {
byte[] byteKey= key.getBytes("UTF8");
KeySpec ks= new DESKeySpec(byteKey);
SecretKeyFactory skf= SecretKeyFactory.getInstance("DES");
SecretKey sk= skf.generateSecret(ks);
Cipher cph=Cipher.getInstance("DES");
cph.init(Cipher.ENCRYPT_MODE, sk);
byte[] byteInString= inString.getBytes("UTF8");
byte[] byteEncoded= cph.doFinal(byteInString);
outString= Base64.encodeToString(byteEncoded, Base64.DEFAULT);
}
catch (UnsupportedEncodingException e){outString="Unable to convert key to byte array.";}
catch (InvalidKeyException e){outString="Unable to generate KeySpec from key";}
catch (NoSuchAlgorithmException e){outString="Unable to find algorithm.";}
catch (InvalidKeySpecException e){outString="Invalid Key Specification";}
//catch (BadPaddingException e){outString="Bad padding";}
//catch (IllegalBlockSizeException e){outString="Illegal block size";}
catch (NoSuchPaddingException e){outString="No such padding";}
catch (IllegalArgumentException e){outString="Illegal argument";}
catch (Exception e){outString=e.getMessage();} //should not get here
return(outString);
}
public String decrypt(String inString, String key){
String outString="DeCrypted7";
try {
byte[] byteKey= key.getBytes("UTF8");
KeySpec ks= new DESKeySpec(byteKey);
SecretKeyFactory skf= SecretKeyFactory.getInstance("DES");
SecretKey sk= skf.generateSecret(ks);
Cipher cph=Cipher.getInstance("DES");
cph.init(Cipher.DECRYPT_MODE, sk);
byte[] byteInString=Base64.decode(inString,Base64.DEFAULT);
byte[] byteDecoded= cph.doFinal(byteInString);
outString= new String(byteDecoded, "UTF8");
}
catch (UnsupportedEncodingException e){outString="Unable to convert key to byte array.";}
catch (InvalidKeyException e){outString="Unable to generate KeySpec from key";}
catch (NoSuchAlgorithmException e){outString="Unable to find algorithm.";}
catch (InvalidKeySpecException e){outString="Invalid Key Specification";}
catch (NoSuchPaddingException e){outString="No such padding";}
catch (BadPaddingException e){outString="Bad padding. Possible Wrong Key, Bad Text, Wrong Mode";}
catch (IllegalBlockSizeException e){outString="Illegal block size";}
catch (IllegalArgumentException e){outString="Illegal argument";}
catch (Exception e){outString=e.getMessage();} // should not get here
return(outString);
}
}
Figure 5. Model code hides calls to DES
package com.google.sites.site.jalcomputing.android.app;
import android.app.Activity;
import android.os.Bundle;
import android.widget.EditText;
import android.view.View;
import android.widget.Button;
public class ConfuseText extends Activity{
private EditText editTextConfusedText;
private EditText editTextPlainText;
private EditText editTextPassword;
private Button buttonConfuseText;
private Button buttonPlainText;
private Model model= new Model();
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// initialize event listeners using anonymous inner classes
buttonConfuseText= (Button)findViewById(R.id.ButtonConfuseText);
editTextConfusedText= (EditText)findViewById(R.id.EditTextConfusedText);
editTextPlainText= (EditText)findViewById(R.id.EditTextPlainText);
editTextPassword= (EditText)findViewById(R.id.EditTextPassword);
buttonPlainText= (Button)findViewById(R.id.ButtonPlainText);
editTextPlainText= (EditText)findViewById(R.id.EditTextPlainText);
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
// CONFUSE TEXT HANDLER
String key, inString, outString;
key= editTextPassword.getText().toString();
inString= editTextPlainText.getText().toString();
outString= model.encrypt(inString, key);
//editTextPlainText.setText("");
editTextConfusedText.setText(outString);
}
});
buttonPlainText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
// GET PLAIN TEXT HANDLER
String key, inString,outString;
key= editTextPassword.getText().toString();
inString= editTextConfusedText.getText().toString();
outString= model.decrypt(inString, key);
//editTextConfusedText.setText("");
editTextPlainText.setText(outString);
}
});
}
}
Figure 7. ConfuseText calling code
Try Methods
In the following code we create a custom return object called BoolString which encapsulates a user friendly error message. We then add two new methods: tryEncrypt and tryDecrypt that return this custom object. (It is also possible to obtain the same functionality by using exceptions. See below.) We use the added information in BoolString to clear the input EditText object on success, protecting the original plain text after encryption. On error, we preserve the original plain text and display a user friendly error message. Finally, we add code to check for a full 8 byte key and return user friendly feedback about invalid keys in a BoolString object. In general, it is not a good idea to pad a key. If you want to accept smaller initial keys, consider using an appropriate hash on the user entered key.
Figure 8. App in action using BoolString object as flag to clear plain text on success
package com.google.sites.site.jalcomputing.android.app;
// a utility class to signal success or failure, return an error message, and return a useful String value
// see Try Out in C#
public final class BoolString {
public final boolean success;
public final String err;
public final String value;
public BoolString(boolean success, String err, String value){
this.success= success;
this.err= err;
this.value= value;
}
}
Figure 9. Our incredibly complex class BoolString!
package com.google.sites.site.jalcomputing.android.app;
import javax.crypto.*;
import java.security.spec.*;
import javax.crypto.spec.*;
import android.util.Base64;
import java.io.*;
import java.security.*;
public class Model {
//private String key;
public BoolString tryEncrypt(String inString, String key){
boolean success= true;
String err="";
String outString="Encrypted"; // BoolString.value
try {
byte[] byteKey= key.getBytes("UTF8");
if (byteKey.length != 8) {
success= false;
err= "Key is "+byteKey.length+" bytes. Key must be exactly 8 bytes in length.";
throw new Exception(err); // could also return here
}
KeySpec ks= new DESKeySpec(byteKey);
SecretKeyFactory skf= SecretKeyFactory.getInstance("DES");
SecretKey sk= skf.generateSecret(ks);
Cipher cph=Cipher.getInstance("DES");
cph.init(Cipher.ENCRYPT_MODE, sk);
byte[] byteInString= inString.getBytes("UTF8");
byte[] byteEncoded= cph.doFinal(byteInString);
outString= Base64.encodeToString(byteEncoded, Base64.DEFAULT);
}
catch (UnsupportedEncodingException e){err="Unable to convert key to byte array."; success= false;}
catch (InvalidKeyException e){err="Unable to generate KeySpec from key";success= false;}
catch (NoSuchAlgorithmException e){err="Unable to find algorithm.";success= false;}
catch (InvalidKeySpecException e){err="Invalid Key Specification";success= false;}
//catch (BadPaddingException e){err="Bad padding";success= false;}
//catch (IllegalBlockSizeException e){err="Illegal block size";success= false;}
catch (NoSuchPaddingException e){err="No such padding";success= false;}
catch (IllegalArgumentException e){err="Illegal argument";success= false;}
catch (Exception e){err=e.getMessage();success= false;}
return new BoolString(success,err,outString);
}
public String encrypt(String inString, String key){
final BoolString bs= tryEncrypt(inString, key);
if (bs.success){return bs.value;}
else {return bs.err;}
}
public BoolString tryDecrypt(String inString, String key){
boolean success= true;
String err="";
String outString="Decrypted"; // BoolString.value
try {
byte[] byteKey= key.getBytes("UTF8");
if (byteKey.length != 8) {
success= false;
err= "Key is "+byteKey.length+" bytes. Key must be exactly 8 bytes in length.";
throw new Exception(err); // could also return here
}
KeySpec ks= new DESKeySpec(byteKey);
SecretKeyFactory skf= SecretKeyFactory.getInstance("DES");
SecretKey sk= skf.generateSecret(ks);
Cipher cph=Cipher.getInstance("DES");
cph.init(Cipher.DECRYPT_MODE, sk);
byte[] byteInString=Base64.decode(inString,Base64.DEFAULT);
byte[] byteDecoded= cph.doFinal(byteInString);
outString= new String(byteDecoded, "UTF8");
}
catch (UnsupportedEncodingException e){err="Unable to convert key to byte array.";success=false;}
catch (InvalidKeyException e){err="Unable to generate KeySpec from key";success=false;}
catch (NoSuchAlgorithmException e){err="Unable to find algorithm.";success=false;}
catch (InvalidKeySpecException e){err="Invalid Key Specification";success=false;}
catch (NoSuchPaddingException e){err="No such padding";success=false;}
catch (BadPaddingException e){err="Bad padding. Possible Wrong Key, Bad Text, Wrong Mode";success=false;}
catch (IllegalBlockSizeException e){err="Illegal block size";success=false;}
catch (IllegalArgumentException e){err="Illegal argument";success=false;}
catch (Exception e){err=e.getMessage();success= false;}
return new BoolString(success,err,outString);
}
public String decrypt(String inString, String key){
final BoolString bs= tryDecrypt(inString, key);
if (bs.success){return bs.value;}
else {return bs.err;}
}
}
Figure 10. Our more complex Model class with tryEncrypt and tryDecrypt methods
package com.google.sites.site.jalcomputing.android.app;
import android.app.Activity;
import android.os.Bundle;
import android.widget.EditText;
import android.view.View;
import android.widget.Button;
public class ConfuseText extends Activity{
private EditText editTextConfusedText;
private EditText editTextPlainText;
private EditText editTextPassword;
private Button buttonConfuseText;
private Button buttonPlainText;
private Model model= new Model();
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// initialize event listeners using anonymous inner classes
buttonConfuseText= (Button)findViewById(R.id.ButtonConfuseText);
editTextConfusedText= (EditText)findViewById(R.id.EditTextConfusedText);
editTextPlainText= (EditText)findViewById(R.id.EditTextPlainText);
editTextPassword= (EditText)findViewById(R.id.EditTextPassword);
buttonPlainText= (Button)findViewById(R.id.ButtonPlainText);
editTextPlainText= (EditText)findViewById(R.id.EditTextPlainText);
buttonConfuseText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
// CONFUSE TEXT HANDLER TRY Version
String key, inString, outString;
boolean success;
key= editTextPassword.getText().toString();
inString= editTextPlainText.getText().toString();
//Method 1 using encrypt
//outString= model.encrypt(inString, key);
//editTextConfusedText.setText(outString);
//Method 2 using tryEncrypt
BoolString bs= model.tryEncrypt(inString, key);
if (bs.success) {
editTextPlainText.setText("");
editTextConfusedText.setText(bs.value);
}
else {
editTextConfusedText.setText(bs.err);
}
}
});
buttonPlainText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
// GET PLAIN TEXT HANDLER
String key, inString,outString;
key= editTextPassword.getText().toString();
inString= editTextConfusedText.getText().toString();
//Method 1 using decrypt
//outString= model.decrypt(inString, key);
//editTextPlainText.setText(outString);
//Method 2 using tryDecrypt
BoolString bs= model.tryDecrypt(inString, key);
if (bs.success) {
editTextPlainText.setText(bs.value);
editTextConfusedText.setText(bs.err);
}
else {
editTextPlainText.setText(bs.err);
}
}
});
}
}
Figure 11. The ConfuseText class that uses information in BoolString to manipulate the user interface on success and failure
Figure 12. The App displaying user friendly error information while preserving the plain text on error
TryMethods in Threads
Consider the advantage of the BoolString when executing the encryption algorithm in a thread. The thread will complete and return the BoolString which can be examined for an error in the calling UI thread!
Exceptional Methods
As I said, you can also send error messages embedded in an exception.
Modify the encrypt and decrypt methods to throw a GeneralSecurityException,
public String encrypt(String inString, String key) throws GeneralSecurityException {
final BoolString bs= tryEncrypt(inString, key);
if (!bs.success){throw new GeneralSecurityException(bs.err);}
return bs.value;
}
public String decrypt(String inString, String key) throws GeneralSecurityException{
final BoolString bs= tryDecrypt(inString, key);
if (!bs.success){throw new GeneralSecurityException(bs.err);}
return bs.value;
}
Then call the code by trapping for the exception.
try {
outString= model.encrypt(inString, key);
editTextPlainText.setText("");
editTextConfusedText.setText(outString);
}
catch(GeneralSecurityException e){
editTextConfusedText.setText(e.getMessage());
}
try {
outString= model.decrypt(inString, key);
editTextPlainText.setText(outString);
}
catch(GeneralSecurityException e){
editTextPlainText.setText(e.getMessage());
}
Using exceptions is the accepted practice.
Base64
Note that the calls to Base64 require Android 2.2 (8). If you wish to program to Android 1.5+, you will need to download a third party library for Base64.
Randomness
You might note that the encrypted text generated from the plain text looks the same each time. Your challenge is to use the initialization vector (IV) and "DES/CBC/PKCS5Padding" to vary the encrypted text.
Why Convert Exception Messages?
The exception messages are converted for user friendly feedback. Let's face it. The average user has no idea what an exception is! So we convert from computer talk to user friendly talk and display THAT message for feedback.
Have fun,
JAL