For information about the design and functionality of the MIDlet, see section Design.
For information about the key aspects of implementing the MIDlet, see:
The MIDlet uses the Cipher
class of the SATSA_CRYPTO
package, since it contains a representation of a cipher that can
be used for encryption and decryption.
To obtain an instance
of the Cipher
class, call the getInstance
method, which passes a transformation string. In the following code
snippet, the getInstance("AES/ECB/PKCS5Padding")
method
requests for a cipher that uses the Advanced Encryption Standard (AES)
algorithm with the Electronic Code Book (ECB) method and the PKCS5
padding scheme.
On the digest = MessageDigest.getInstance("SHA-1")
code line, the getInstance
method returns an instance
of the MessageDigest
class based on the digest algorithm's
name, and the digest is calculated using the SHA-1 algorithm.
The actual digest calculation is performed further down by using
the digest
method.
Codec() { 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; } }
Use the SecretKeySpec
class to
build a key from a byte array. The method initialises the key from
a password by creating an instance of the SecretKeySpec
class. It then calculates the size of the cipher text and passes
the Key
object to the Cipher
using
the init
method. With the Cipher
class initialised, the method proceeds to feed data using the doFinal
method.
// 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"); } }
Use the decrypt
method
to decrypt an encrypted message. The method initialises
the cipher in decrypt mode and decrypts the array using the key made
from the password passed to it. The method then calculates the length
of the actual text that was stored, copies that part of the array
to a new byte array, and returns the byte array.
// 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"); } }
The digest
method returns a SHA-1
digest of the byte array passed to it. The digest is used to verify
the user's message after it has been decrypted.
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"); } }
Use the SATSAMIDlet
class to make the call to encrypt or decrypt the message. The actual
encryption or decryption is run in a separate thread.
// 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(); } // Shows a decrypted message 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(); }
The CryptoRunnable
class is an
inner class that handles the decryption and encryption process in
a separate thread.
To encrypt a message, use the following code:
// 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();
To decrypt a message, use the following code:
} 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()); } } } }