This section provides the source code for the SATSA MIDlet. For the complete Eclipse project ZIP file , see Forum Nokia.
The example includes the following classes:
package satsa; import java.security.GeneralSecurityException; import javax.crypto.BadPaddingException; import javax.microedition.lcdui.Display; import javax.microedition.midlet.MIDlet; import javax.microedition.midlet.MIDletStateChangeException; // Main SATSA MIDlet class // It controls the state of the user interface // and handles the encryption and decryption process public class SATSAMIDlet extends MIDlet { private Display display; private MessageStore messageStore; private ListScreen listScreen; private EncryptScreen encryptScreen; private PasswordScreen passwordScreen; private DecryptScreen decryptScreen; private InfoScreen infoScreen; private NewScreen newScreen; private Codec codec; public SATSAMIDlet() {} protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { exitMIDlet(); } protected void pauseApp() { } protected void startApp() throws MIDletStateChangeException { if (display == null) { initMIDlet(); } } // Initialize all the internal fields private void initMIDlet() { // Init the user interface display = Display.getDisplay(this); listScreen = new ListScreen(this); encryptScreen = new EncryptScreen(this); passwordScreen = new PasswordScreen(this); decryptScreen = new DecryptScreen(this); infoScreen = new InfoScreen(); newScreen = new NewScreen(this); // Initialize the Cryptographic Codec and the message store codec = new Codec(this); messageStore = new MessageStore(this); // Startup the UI showMessageList(); } // Shows the list of existing messages void showMessageList() { messageStore.fillList(listScreen); display.setCurrent(listScreen); } // Shows an encrypted message in HEX format void showEncryptedMessage(int index) { encryptScreen.setIndex(index); encryptScreen.setMessage(codec .byteToHex(messageStore.getMessage(index+1))); display.setCurrent(encryptScreen); } // Displays the password screen void showPasswordScreen(int index) { passwordScreen.setIndex(index); display.setCurrent(passwordScreen); } // Shows a decrypted messages void showDecryptedMessage(int index, String password) { // 128bit (16 characters) key is needed for AES // just fill with blanks if too short while (password.length() <16 ) { password = password.concat(" "); } decryptScreen.setIndex(index); byte message[] = messageStore.getMessage(index+1); // Do the decryption and validation in a separate thread new Thread(new CryptoRunnable(message, password.getBytes(), false)).start(); } // Display an error message void showError(String messageString) { infoScreen.showError(messageString, Display.getDisplay(this)); } // Delete a message void deleteMessage(int index) { messageStore.deleteMessage(index+1); messageStore.fillList(listScreen); } // Shows the new message void showNewMessage() { newScreen.createForm(); display.setCurrent(newScreen); } // adds a new message encrypting it an storing in the record store void addNewMessage(String message, String password) { // 128bit (16 characters) key is needed for AES // just fill with blanks if too short while (password.length() <16 ) { password = password.concat(" "); } // Do the encryption in a separate thread new Thread(new CryptoRunnable(message.getBytes(), password.getBytes(), true)).start(); } void exitMIDlet() { if (messageStore != null) { messageStore.close(); } notifyDestroyed(); } // Internal class to handle the decryption and encryption // process in a separate thread as recommended in SATSA-CRYPTO private class CryptoRunnable implements Runnable { private byte[] message, password; private boolean encryption; CryptoRunnable(byte[] message, byte[] password, boolean encryption) { this.message = message; this.password = password; this.encryption = encryption; } public void run() { if (encryption) { try { // Encrypt the message byte[] encryptedMessage = codec.encrypt(password, message); // Calculate the digest of the encrypted message byte[] digest = codec.digest(encryptedMessage); // Compose the overall message byte[] messageBytes = new byte[encryptedMessage.length + digest.length]; System.arraycopy(digest, 0, messageBytes, 0, digest.length); System.arraycopy(encryptedMessage, 0, messageBytes, digest.length, encryptedMessage.length); // Store the encoded message messageStore.addMessage(messageBytes); } catch (GeneralSecurityException gse) { // Use of generic Exception type since mutiple // exceptions may be thrown at this point showError("General Security Exception while encrypting: " + gse.toString()); } showMessageList(); } else { try { // Get the cipher text and the digest // SHA-1 digest is 160 bits long byte[] digest = new byte[20]; byte[] cipherText = new byte[message.length-digest.length]; // Decompose the message System.arraycopy(message, 0, digest, 0, digest.length); System.arraycopy(message, digest.length, cipherText, 0, cipherText.length); // Verify the cipher's text digest if (codec.isDigestValid(cipherText, digest)) { // If digest is ok, let's decrypt byte[] plainText = codec.decrypt(password, cipherText); // Display the message on screen decryptScreen.setMessage(new String(plainText)); display.setCurrent(decryptScreen); } else { showError("Digest of message is not valid"); } } catch (BadPaddingException bpe) { // This is a particular exception when the password is incorrect showError("Incorrect password."); } catch (GeneralSecurityException gse) { // Handles all other exceptions showError("General Security Exception while decrypting: " + gse.toString()); } } } } }
package satsa; import java.security.DigestException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.Key; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.spec.InvalidKeySpecException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.ShortBufferException; import javax.crypto.spec.SecretKeySpec; // Utility class that encapsulates the calls to the SATSA API class Codec { // we can have a global instance of the algorithms // as long as we synchronize access private MessageDigest digest = null; private Cipher cipher = null; private boolean operative = true; private SATSAMIDlet midlet; // Builds the Codec object and initializes the // cipher and digest Codec(SATSAMIDlet midlet) { this.midlet = midlet; try { digest = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { // Basically this should not happen since we have to know // if SHA-1 is availabe otherwise the whole design // cannot work operative = false; } try { cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); } catch (NoSuchAlgorithmException e) { // This should not happen since we know the target platform // but we set the operative flag to false just in case operative = false; } catch (NoSuchPaddingException e) { // This should not happen since we know the target platform // but we set the operative flag to false just in case operative = false; } } // Encrypt text with given AES key. It encodes the message // including the length in two bytes and the plaintext synchronized byte[] encrypt (byte[] keyBits, byte[] plaintext) throws InvalidKeySpecException, InvalidKeyException, IllegalStateException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { if (operative) { // Initialize the key from the password Key key = new SecretKeySpec(keyBits, 0, keyBits.length, "AES"); // add 2 bytes to encode the length of the plaintext // as a short value byte[] plaintextAndLength = new byte[plaintext.length + 2]; plaintextAndLength[0] = (byte)(0xff & (plaintext.length >> 8)); plaintextAndLength[1] = (byte)(0xff & plaintext.length); // build the new plaintext System.arraycopy(plaintext, 0, plaintextAndLength, 2, plaintext.length); // calculate the size of the ciperthext considering // the padding int blocksize = 16; int ciphertextLength = 0; int remainder = plaintextAndLength.length % blocksize; if (remainder == 0) { ciphertextLength = plaintextAndLength.length; } else { ciphertextLength = plaintextAndLength.length - remainder + blocksize; } byte[] cipherText = new byte[ciphertextLength]; // reinitialize the cipher in encryption mode with the given key cipher.init(Cipher.ENCRYPT_MODE, key); // do the encryption cipher.doFinal(plaintextAndLength, 0, plaintextAndLength.length, cipherText, 0); return cipherText; } else { throw new IllegalStateException("Codec not initialized"); } } synchronized byte[] digest(byte message[]) throws DigestException { if (operative) { // Reset the digest and update with the data digest.reset(); digest.update(message, 0, message.length); // SHA-1 produces 160-bit long digests byte[] output = new byte[20]; digest.digest(output, 0, output.length); return output; } else { throw new IllegalStateException("Codec not initialized"); } } synchronized boolean isDigestValid(byte message[], byte[] digest) throws DigestException { if (operative) { byte[] calculatedDigest = digest(message); if (calculatedDigest.length != digest.length) { return false; } // compare byte per byte for (int i=0;i<digest.length;i++) { if (calculatedDigest[i] != digest[i]) { return false; } } return true; } else { throw new IllegalStateException("Codec not initialized"); } } // Decrypt text with given AES key. It decodes the message // reading the message length and then the message itself synchronized byte[] decrypt (byte[] keyBits, byte[] cipherText) throws InvalidKeySpecException, InvalidKeyException, IllegalStateException, ShortBufferException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { if (operative) { // create a key from the keyBits Key key = new SecretKeySpec(keyBits, 0, keyBits.length, "AES"); // Initialize the cipher in decrypt mode cipher.init(Cipher.DECRYPT_MODE, key); byte[] decrypted = new byte[cipherText.length]; // Decrypt the cipher text cipher.doFinal(cipherText, 0, cipherText.length, decrypted, 0); // Calculate the length of the plaintext int plainTextLength = (decrypted[0] << 8) | (decrypted[1] & 0xff); byte[] finalText = new byte[plainTextLength]; // Decode the final text System.arraycopy(decrypted, 2, finalText, 0, plainTextLength); return finalText; } else { throw new IllegalStateException("Codec not initialized"); } } // Displays ecrypted data in hex String byteToHex(byte[] data) { StringBuffer hexString = new StringBuffer(); String hexCodes = "0123456789ABCDEF"; for (int i=0; i < data.length; i++) { hexString.append( hexCodes.charAt( (data[i] >> 4) & 0x0f) ); hexString.append( hexCodes.charAt( data[i] & 0x0f) ); if (i< data.length - 1) { hexString.append(":"); } if ( ((i+1)%8) == 0) hexString.append("\n"); } return hexString.toString(); } }
package satsa; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.StringItem; class DecryptScreen extends Form implements CommandListener { private final SATSAMIDlet midlet; private final Command commandBack = new Command("Back", Command.BACK, 1); private int index; private String decryptedMessage; DecryptScreen(SATSAMIDlet midlet) { super(null); this.midlet = midlet; createForm(); } public void commandAction(Command c, Displayable d) { if (c==commandBack) { midlet.showEncryptedMessage(index); } } void setIndex(int index) { this.index = index; } void setMessage(String decryptedMessage) { this.decryptedMessage = decryptedMessage.trim(); createForm(); } private void createForm() { deleteAll(); setTitle("Decrypted Message "); StringItem messageItem = new StringItem(null, decryptedMessage); append(messageItem); addCommand(commandBack); setCommandListener(this); } }
package satsa; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.StringItem; // Screen used to display a decrypted message class EncryptScreen extends Form implements CommandListener { private final SATSAMIDlet midlet; private final Command commandBack = new Command("Back", Command.BACK, 1); private final Command commandShowDecrypted = new Command("Decrypt", Command.ITEM, 1); private int index; private String encryptedMessage; EncryptScreen(SATSAMIDlet midlet) { super(null); this.midlet = midlet; createForm(); } public void commandAction(Command c, Displayable d) { if (c==commandBack) { midlet.showMessageList(); } if (c==commandShowDecrypted) { midlet.showPasswordScreen(index); } } void setIndex(int index) { this.index = index; } void setMessage(String encryptedMessage) { this.encryptedMessage = encryptedMessage; createForm(); } private void createForm() { deleteAll(); setTitle("Encrypted Message "); StringItem messageItem = new StringItem(null, encryptedMessage); append(messageItem); addCommand(commandBack); addCommand(commandShowDecrypted); setCommandListener(this); } }
package satsa; import javax.microedition.lcdui.Alert; import javax.microedition.lcdui.AlertType; import javax.microedition.lcdui.Display; class InfoScreen extends Alert { InfoScreen() { super("InfoScreen"); } void showError(String message, Display display) { setTitle("Error"); setType(AlertType.ERROR); setTimeout(5000); setString(message); display.setCurrent(this); } }
package satsa; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.List; // Main UI screen showing the list of available messages class ListScreen extends List implements CommandListener { private final SATSAMIDlet midlet; private final Command commandExit = new Command("Exit", Command.EXIT, 1); private final Command commandShow = new Command("Show", Command.ITEM, 1); private final Command commandNew = new Command("Create new", Command.ITEM, 2); private final Command commandDelete = new Command("Delete this", Command.ITEM, 3); ListScreen(SATSAMIDlet midlet) { super("Messages", List.IMPLICIT); this.midlet = midlet; createList(); } public void commandAction(Command c, Displayable d) { if (c==commandExit) { midlet.exitMIDlet(); } if (c==commandShow) { midlet.showEncryptedMessage(getSelectedIndex()); } if (c==commandDelete) { midlet.deleteMessage(getSelectedIndex()); } if (c==commandNew) { midlet.showNewMessage(); } } void setNumber(int number) { deleteAll(); for(int i=1; i<=number; i++) { append("Message "+i, null); } } private void createList() { addCommand(commandExit); addCommand(commandDelete); addCommand(commandNew); addCommand(commandShow); setCommandListener(this); } }
package satsa; import javax.microedition.lcdui.List; import javax.microedition.rms.RecordEnumeration; import javax.microedition.rms.RecordStore; import javax.microedition.rms.RecordStoreException; // Stores the messages as byte arrays in the RMS class MessageStore { private final SATSAMIDlet midlet; private RecordStore recordStore; private RecordEnumeration recordEnumeration; private int[] messageIdArray; private int numberOfMessages; MessageStore(SATSAMIDlet midlet) { this.midlet = midlet; openMessageStore(); } void close() { try { recordStore.closeRecordStore(); } catch (RecordStoreException rse) { // Ignore, since the application is closing anyway. } } // List all messages in Record Store void fillList(List list) { list.deleteAll(); try { numberOfMessages = recordStore.getNumRecords(); messageIdArray = new int[numberOfMessages+1]; recordEnumeration = recordStore.enumerateRecords(null, null, false); int i=0; while (recordEnumeration.hasNextElement()) { i++; int id=recordEnumeration.nextRecordId(); list.append("Message " + id, null); messageIdArray[i]=id; } } catch (RecordStoreException rse) { midlet.showError("Record Store Exception. " + rse.getMessage()); } } // Add a record to Record Store void addMessage(byte[] messageData) { try { recordStore.addRecord(messageData, 0, messageData.length); } catch (RecordStoreException rse) { midlet.showError("Record Store Exception. " + rse.getMessage()); } } // Get message from Record Store byte[] getMessage(int index) { try { byte[] messageData = recordStore.getRecord(messageIdArray[index]); return messageData; } catch (RecordStoreException rse) { midlet.showError("Record Store Exception. " + rse.getMessage()); return null; } } // Delete a record from Record Store void deleteMessage(int index) { try { recordStore.deleteRecord(messageIdArray[index]); } catch (RecordStoreException rse) { midlet.showError("Record Store Exception. " + rse.getMessage()); } } private void openMessageStore() { try { recordStore = RecordStore.openRecordStore("SATSA_test", true); } catch (RecordStoreException rse) { midlet.showError("Record Store Exception. " + rse.getMessage()); } } }
package satsa; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.TextField; // Displays a form to enter new messages class NewScreen extends Form implements CommandListener { private final SATSAMIDlet midlet; private final Command commandBack = new Command("Back", Command.BACK, 1); private final Command commandSave = new Command("Save", Command.ITEM, 1); TextField messageField; TextField passwordField; NewScreen(SATSAMIDlet midlet) { super(null); this.midlet = midlet; } public void commandAction(Command c, Displayable d) { if (c==commandBack) { midlet.showMessageList(); } if (c==commandSave) { midlet.addNewMessage(messageField.getString(), passwordField.getString()); } } void createForm() { deleteAll(); setTitle("Add new message "); messageField = new TextField("Message", "", 80, TextField.ANY); // Notice that the max length is 16 as indicated for the AES key passwordField = new TextField("Password", "", 16, TextField.PASSWORD); append(messageField); append(passwordField); addCommand(commandBack); addCommand(commandSave); setCommandListener(this); } }
package satsa; import javax.microedition.lcdui.Command; import javax.microedition.lcdui.CommandListener; import javax.microedition.lcdui.Displayable; import javax.microedition.lcdui.Form; import javax.microedition.lcdui.TextField; // Shows a password field used to decrypt a message class PasswordScreen extends Form implements CommandListener { private final SATSAMIDlet midlet; private final Command commandBack = new Command("Back", Command.BACK, 1); private final Command commandDecrypt = new Command("Decrypt", Command.ITEM, 1); private int index; TextField passwordField; PasswordScreen(SATSAMIDlet midlet) { super(null); this.midlet = midlet; createForm(); } public void commandAction(Command c, Displayable d) { if (c==commandBack) { midlet.showEncryptedMessage(index); } if (c==commandDecrypt) { midlet.showDecryptedMessage(index, passwordField.getString()); } } void setIndex(int index) { this.index = index; createForm(); } private void createForm() { deleteAll(); setTitle("Enter password to decrypt."); passwordField = new TextField("Password", "", 16, TextField.PASSWORD); append(passwordField); addCommand(commandBack); addCommand(commandDecrypt); setCommandListener(this); } }