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()); } } } }