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 −−
the write
methods, the available
method, and the close
method, which all provide the semantics you would expect. Often, of course, these methods are never used directly −− for example, if youre
sending text data over a socket, you will wrap a cipher output stream around the sockets output stream, but then you will wrap a print writer around that; the programming interface then becomes a series of calls to the
print and
println methods. You can use any similar output stream to get a different interface.
It does not matter if the cipher object that was passed to the constructor does automatic padding or not −− the CipherOutputStream
class itself does not make that restriction. As a practical matter, however, youll want to use a padding cipher object, since otherwise youll be responsible for keeping track of the amount of
data passed to the output stream and tacking on your own padding. Usually, the better alternative is to use a byte−oriented mode such as CFB8. This is particularly true in
streams that are going to be used conversationally: a message is sent, a response received, and then another message is sent, etc. In this case, you want to make sure that the entire message is sent; you cannot allow the
cipher to buffer any data internally while it waits for a full block to arrive. And, for reasons were just about to describe, you cannot call the
flush method in this case either. Hence, you need to use a streaming
cipher or, technically, a block cipher in streaming mode in this case. When the
flush method is called on a
CipherOutputStream either directly or because the stream
is being closed, the padding of the stream comes into play. If the cipher is automatically padding, the padding bytes are generated in the
flush method. If the cipher is not automatically padding and the number of
bytes that have been sent through the stream is not a multiple of the ciphers block size, then the flush
method or indirectly the close
method throws an IllegalBlockSizeException
note that this requires that the
IllegalBlockSizeException be a runtime exception.
If the cipher is performing padding, it is very important not to call the flush
method unless it is immediately followed by a call to the
close method. If the
flush method is called in the middle
of processing data, padding is added in the middle of the data. This means the data does not decrypt correctly. Remember that certain output streams especially some types of
PrintWriter streams flush
automatically; if youre using a padding cipher, dont use one of those output streams. We can use this class to write some encrypted data to a file like this:
package javasec.samples.ch13; import java.io.;
import java.security.; import javax.crypto.;
import javax.crypto.spec.; public class Send {
public static void mainString args[] { try {
KeyGenerator kg = KeyGenerator.getInstanceDES; kg.initnew SecureRandom ;
SecretKey key = kg.generateKey ; SecretKeyFactory skf = SecretKeyFactory.getInstanceDES;
Class spec = Class.forNamejavax.crypto.spec.DESKeySpec; DESKeySpec ks = DESKeySpec skf.getKeySpeckey, spec;
ObjectOutputStream oos = new ObjectOutputStream new FileOutputStreamkeyfile;
oos.writeObjectks.getKey ; Cipher c = Cipher.getInstanceDESCFB8NoPadding;
c.initCipher.ENCRYPT_MODE, key; CipherOutputStream cos = new CipherOutputStream
new FileOutputStreamciphertext, c;