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 −−