Using the Cipher Class for EncryptionDecryption

byte 0xea, byte 0xf2 }; PBEParameterSpec paramSpec = new PBEParameterSpecsalt, 20; PBEKeySpec keySpec = new PBEKeySpecpassword.toCharArray ; SecretKeyFactory kf = SecretKeyFactory.getInstancePBEWithMD5AndDES; SecretKey passwordKey = kf.generateSecretkeySpec; Cipher c = Cipher.getInstancePBEWithMD5AndDES; c.initCipher.WRAP_MODE, passwordKey, paramSpec; byte[] wrappedKey = c.wrapsharedKey; Now well initialize a cipher with the DESede key and encrypt some data with it. c = Cipher.getInstanceDESede; c.initCipher.ENCRYPT_MODE, sharedKey; byte[] input = Stand and unfold yourself.getBytes ; byte[] encrypted = c.doFinalinput; Now well try to read the wrapped key and the encrypted data First, we have to unwrap the key. c = Cipher.getInstancePBEWithMD5AndDES; Well reuse the key and param spec from before, but generally wed have to recreate it from the password. c.initCipher.UNWRAP_MODE, passwordKey, paramSpec; Key unwrappedKey = c.unwrapwrappedKey, DESede, Cipher.SECRET_KEY; Now we can use the unwrapped key to decrypt the data. c = Cipher.getInstanceDESede; c.initCipher.DECRYPT_MODE, unwrappedKey; String newData = new Stringc.doFinalencrypted; System.out.printlnThe string was + newData; } }

13.1.5 Implementing the Cipher Class

As with all Java 2 engines, the SPI for the Cipher class is a separate class, the CipherSpi class javax.crypto.CipherSpi : public abstract class CipherSpi The SPI for the Cipher class. This class is responsible for performing the encryption or decryption according to its internal algorithm. Support for various modes or padding schemes must be handled by this class as well. There is very little intelligence in the Cipher class itself; virtually all of its methods are simply passed through calls to corresponding methods in the SPI. The one exception to this is the getInstance method, which is responsible for parsing the algorithm string and removing the mode and padding strings if present. If it finds a mode and padding specification, it calls these methods of the SPI: public abstract void engineSetModeString s Set the mode of the cipher engine according to the specified string. If the given mode is not supported by this cipher, a NoSuchAlgorithmException should be thrown. public abstract void engineSetPaddingString s Set the padding scheme of the cipher engine according to the specified string. If the given padding scheme is not supported by this cipher, a NoSuchPaddingException should be thrown. Remember that the mode and padding strings we looked at earlier are specific to the implementation of the SunJCE security provider. Hence, while ECB is a common mode specification, it is completely at the discretion of your implementation whether that string should be recognized or not. If you choose to implement a common mode, it is recommended that you use the standard strings, but you may use any naming convention that you find attractive. The same is true of padding schemes. Complicating this matter is the fact that there are no classes in JCE that assist you with implementing any mode or padding scheme. So if you need to support a mode or padding scheme, you must write the required code from scratch. The remaining methods of the SPI are all called directly from the corresponding methods of the Cipher class: protected abstract int engineGetBlockSize Return the number of bytes that comprise a block for this engine. Unless the cipher is capable of performing padding, input data for this engine must total a multiple of this block size though individual calls to the update method do not necessarily have to provide data in block−sized chunks. protected abstract byte[] engineGetIV Return the initialization vector that was used to initialize the cipher. If the cipher was in a mode where no initialization vector was required, this method should return null . protected abstract int engineGetOutputSizeint inputSize Return the number of bytes that the cipher will produce if the given amount of data is fed to the cipher. This method should take into account any data that is presently being buffered by the cipher as well as any padding that may need to be added if the cipher is performing padding. protected void engineInitint op, Key key, SecureRandom sr protected void engineInitint op, Key key, AlgorithmParameterSpec aps, SecureRandom sr protected void engineInitint op, Key key, AlgorithmParameters ap, SecureRandom sr Initialize the cipher based on the op , which will be either Cipher.ENCRYPT_MODE , Cipher.DECRYPT_MODE , Cipher.WRAP_MODE , or Cipher.UNWRAP_MODE . This method should ensure that the key is of the correct type and throw an InvalidKeyException if it is not or if it is otherwise invalid, and use the given random number generator and algorithm parameters, if applicable to initialize its internal state. If algorithm parameters are provided but not supported or are otherwise invalid, this method should throw an InvalidAlgorithmParameterException . protected abstract byte[] engineUpdateint input[], int offset, int len protected abstract int engineUpdateint input[], int offset, int len, byte[] output, int outOff Encrypt or decrypt the input data. The data that is passed to these methods is not necessarily an integral number of blocks. It is the responsibility of these methods to process as much of the input data as possible and to buffer the remaining data internally. Upon the next call to an Chapter 13. Cipher−Based Encryption engineUpdate or engineDoFinal method, this buffered data must be processed first, followed by the input data of that method and again leaving any leftover data in an internal buffer. protected abstract byte[] engineDoFinalint input[], int offset, int len protected abstract int engineDoFinalint input[], int offset, int len, byte[] output, int outOff Encrypt or decrypt the input data. Like the update method, this method must consume any buffered data before processing the input data. However, since this is the final set of data to be processed, this method must make sure that the total amount of data has been an integral number of blocks; it should not leave any data in its internal buffers. If the cipher supports padding and padding was requested through the engineSetPadding method, this method should perform the required padding; an error in padding should cause a BadPaddingException to be thrown. Otherwise, if padding is not being performed and the total amount of data has not been an integral number of blocks, this method should throw an IllegalBlockSizeException . protected byte[] engineWrapKey key Wrap the key, returning the encrypted bytes that can be used to reconstitute the key. Note that this method is not abstract for backward−compatibility reasons. You must override it if you want to support key wrapping; otherwise it will throw an UnsupportedOperationException . If the key is not the correct format, you should throw an InvalidKeyException ; if the cipher cannot handle the correct block size of the key then it should throw an IllegalBlockSizeException . protected Key engineUnwrapbyte[] key, String algorithm, int type Unwrap the key bytes, returning the reconstituted key. Note that this method is not abstract for backward−compatibility reasons; by default it throws an UnsupportedOperationException . It should throw a NoSuchAlgorithmException if the algorithm type isnt supported, or an InvalidKeyException if the key cant be unwrapped. Using our typical XOR strategy of encryption, heres a simple implementation of a cipher engine: package javasec.samples.ch13; import java.security.; import java.security.spec.; import javax.crypto.; import javasec.samples.ch08.XYZProvider; import javasec.samples.ch09.XORKey; public class XORCipher extends CipherSpi { byte xorByte; public XORCipher { XYZProvider.verifyForJCE ; } protected void engineInitint i, Key k, SecureRandom sr throws InvalidKeyException { if k instanceof XORKey throw new InvalidKeyExceptionXOR requires an XOR key; xorByte = k.getEncoded [0]; } protected void engineInitint i, Key k, AlgorithmParameterSpec aps, SecureRandom sr throws InvalidKeyException, InvalidAlgorithmParameterException { throw new InvalidAlgorithmParameterException Algorithm parameters not supported in this class; } protected void engineInitint i, Key k, AlgorithmParameters ap, SecureRandom sr throws InvalidKeyException, InvalidAlgorithmParameterException { throw new InvalidAlgorithmParameterException Algorithm parameters not supported in this class; } protected byte[] engineUpdatebyte in[], int off, int len { return engineDoFinalin, off, len; } protected int engineUpdatebyte in[], int inoff, int length, byte out[], int outoff { for int i = 0; i length; i++ out[outoff + i] = byte in[inoff + i] xorByte; return length; } protected byte[] engineDoFinalbyte in[], int off, int len { byte out[] = new byte[len − off]; engineUpdatein, off, len, out, 0; return out; } protected int engineDoFinalbyte in[], int inoff, int len, byte out[], int outoff { return engineUpdatein, inoff, len, out, outoff; } protected int engineGetBlockSize { return 1; } protected byte[] engineGetIV { return null; } protected int engineGetOutputSizeint sz { return sz; } protected void engineSetModeString s throws NoSuchAlgorithmException { throw new NoSuchAlgorithmExceptionUnsupported mode + s; } protected void engineSetPaddingString s throws NoSuchPaddingException { throw new NoSuchPaddingExceptionUnsupported padding + s; } protected AlgorithmParameters engineGetParameters { return null; } } The bulk of the work of any cipher engine will be in the engineUpdate method, which is responsible for actually providing the ciphertext or plaintext. In this case, weve simply XORed the key value with every byte, a process that works both for encryption as well as decryption. Because the work done by the engineUpdate method is so symmetrical, we dont need to keep track internally of whether were encrypting or decrypting; for us, the work is always the same. For some algorithms, you may need to keep track of the state of the cipher by setting an internal variable when the engineInit method is called. Similarly, because we can operate on individual bytes at a time, we didnt have to worry about padding and buffering internal data. Such an extension is easy using the code we showed earlier that uses the modulus operator to group the input arrays into blocks. To use this class, we would need to use the XYZProvider class we developed in Chapter 8. Then we simply instantiate the engine and create the cipher like this: Security.addProvidernew XYZProvider ; KeyGenerator kg = KeyGenerator.getInstanceXOR; Cipher c = Cipher.getInstanceXOR; Note that XOR is the valid algorithm name for this implementation since we do not support any modes or padding schemes. Note too that we no longer need an initialization vector to create the cipher. Finally, remember that the Cipher class is a JCE engine, which is why the constructor here calls the verifyForJCE method.

13.2 Cipher Streams

In the Cipher class we just examined, we had to provide the data to be encrypted or decrypted as multiple blocks of data. This is not necessarily the best interface for programmers: what if we want to send and receive arbitrary streams of data over the network? It would often be inconvenient to get all the data into buffers before it can be encrypted or decrypted. The solution to this problem is the ability to associate a cipher object with an input or output stream. When data is written to such an output stream, it is automatically encrypted, and when data is read from such an input stream, it is automatically decrypted. This allows a developer to use Javas normal semantics of nested filter streams to send and receive encrypted data.

13.2.1 The CipherOutputStream Class

The class that encrypts data on output to a stream is the CipherOutputStream class javax.crypto.CipherOutputStream : public class CipherOutputStream extends FilterOutputStream Provide a class that will encrypt data as it is written to the underlying stream. Like all classes that extend the FilterOutputStream class, constructing a cipher output stream requires that an existing output stream has already been created. This allows us to use the existing output stream from a socket or a file as the destination stream for the encrypted data: public CipherOutputStreamOutputStream outputStream, Cipher cipher Create a cipher output stream, associating the given cipher object with the existing output stream. The given cipher must already have been initialized, or an IllegalStateException will be thrown. The output stream may be operated on with any of the methods from the FilterOutputStream class −−