Implementing the Codec class

This is the class that handles the cryptographic operations. The AES algorithm used here works in sequences of 128 bits. This means that the length of the cipher will be a multiple of 16 bytes. Before encryption, the right value for the cipher text's buffer length is properly calculated.

To create the class:

  1. Assign the class to the package satsa and import the required classes.

    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;
  2. The class Cipher of the SATSA_CRYPTO package is used here as it contains a representation of a cipher that can be used for encryption and decryption. To obtain an instance of Cipher, call the getInstance method passing a transformation string. Here getInstance("AES/ECB/PKCS5Padding") requests for a Cipher that uses the Advanced Encryption Standard [AES] algorithm using the Electronic Code Book (ECB) method and the PKCS5 padding scheme.

    On the line digest = MessageDigest.getInstance("SHA-1"), the getInstance method will return an instance of the MessageDigest class based on the digest algorithm's name and the digest is calculated using the SHA-1 algorithm. Notice that the digest is actually calculated further below by using the digest method.

    // 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 available 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;
        }
      }  
  3. Implement the functionality for encrypting messages.

    Use the SecretKeySpec class to build a key from a byte array used for symmetric encryption algorithms that need no parameters. Initialize the key from a password by creating an instance of the SecretKeySpec class. Then calculate the size of the cipher text and pass the Key object to the Cipher using the init method. With the Cipher initialized, proceed to feed data using the doFinal method.

    The digest is calculated by using the digest method. To do this, a byte array containing the message is passed to the update 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 ciphertext 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");
        }    
      }
  4. Implement the functionality for decrypting messages.

    Use the SecretKeySpec class to build a key from a byte array. Pass the Key object to the Cipher using the init method. With the Cipher initialized, proceed to feed data using the doFinal method. Also calculate the plain text and then decode the final text.

      // 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");
        }  
      }
  5. Display the encrypted data in hex.

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