Codec.java

/**
 * Copyright (c) 2012 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia 
Corporation.
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners.
 * See LICENSE.TXT for license information.
 */

package com.nokia.example.satsa;

import java.security.*;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.*;
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 synchronise access

    private MessageDigest digest = null;
    private Cipher cipher = null;
    private boolean operative = true;

    // Builds the Codec object and initializes the
    // cipher and digest 
    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;
        }
    }

    // Encrypt text with given AES key. It encodes the message
    // including the length in two bytes and the plaintext
    synchronised 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");
        }
    }

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

    }

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