Implementation

For information about the design and functionality of the MIDlet, see section Design.

For information about the key aspects of implementing the MIDlet, see:

Getting a cipher and key

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

Decrypting encrypted messages

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

    }

Decrypting and encrypting messages

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