Cipher Streams

The Cipher class is the engine that powers encryption. Example 12-5 showed how this class could be used to encrypt and decrypt data read from a stream. The javax.crypto package also provides CipherInputStream and CipherOutputStream filter streams that use a Cipher object to encrypt or decrypt data passed through the stream. Like DigestInputStream and DigestOutputStream, they aren't a great deal of use in themselves. However, you can chain them in the middle of several other streams. For example, if you chain a GZIPOutputStream to a CipherOutputStream that is chained to a FileOutputStream, you can compress, encrypt, and write to a file, all with a single call to write( ), as shown in Figure 12-3. Similarly, you might read from a URL with the input stream returned by openStream( ), decrypt the data read with a CipherInputStream, check the decrypted data with a MessageDigestInputStream, and finally pass it all into an InputStreamReader for conversion from Latin-1 to Unicode. On the other side of the connection, a web server could read a file from its hard drive, write the file onto a socket with an output stream, calculate a digest with a DigestOutputStream, and encrypt the file with a CipherOutputStream.

Figure 12-3. The CipherOutputStream in the middle of a chain of filters

 

12.6.1. CipherInputStream

CipherInputStream is a subclass of FilterInputStream.

public class CipherInputStream extends FilterInputStream

CipherInputStream has all the usual methods of any input stream, like read( ), skip( ), and close( ). It overrides seven of these methods to do its filtering. These methods are all invoked much as they would be for any other input stream. However, as the data is read, the stream's Cipher object either decrypts or encrypts the data. (Assuming your program wants to work with unencrypted data, as is most commonly the case, the cipher input stream will decrypt the data.)

A CipherInputStream object contains a Cipher object that's used to decrypt or encrypt all data read from the underlying stream before passing it to the eventual source. This Cipher object is set in the constructor. Like all filter stream constructors, this constructor takes another input stream as an argument:

public CipherInputStream(InputStream in, Cipher c)

The Cipher object c must be a properly initialized instance of javax.crypto.Cipher, most likely returned by Cipher.getInstance( ). This Cipher object must also have been initialized for either encryption or decryption with init( ) before being passed into the constructor. There is also a protected constructor that might be used by subclasses that want to implement their own, non-JCE-based encryption scheme:

protected CipherInputStream(InputStream in)

CipherInputStream overrides most methods declared in FilterInputStream. Each of these makes the necessary adjustments to handle encrypted data. For example, skip( ) skips the number of bytes after encryption or decryption, which is important if the ciphertext does not have the same length as the plaintext. The available( ) method also returns the number of bytes available after encryption or decryption. The markSupported( ) method returns false; you cannot mark and reset a cipher input stream, even if the underlying class supports marking and resetting. Allowing this would confuse many encryption algorithms. However, you can make a cipher input stream the underlying stream of another class like BufferedInputStream, which does support marking and resetting.

Strong encryption schemes have the distinct disadvantage that changing even a single bit in the data can render the entire file unrecoverable gibberish. Therefore, it's useful to combine encryption with a digest so you can tell whether a file has been modified. Example 12-6 uses CipherInputStream to DES-encrypt a file named on the command line, but that's not all. The ciphertext is also digested and the digest saved so corruption can be detected.

Example 12-6. Digest Encryptor

import java.io.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class DigestEncryptor {

 public static void main(String[] args)
 throws IOException, GeneralSecurityException {
 if (args.length != 2) {
 System.err.println("Usage: java DigestEncryptor filename password");
 return;
 }
 String filename = args[0];
 String password = args[1];
 if (password.length( ) < 8 ) {
 System.err.println("Password must be at least eight characters long");
 }
 FileInputStream fin = new FileInputStream(filename);
 FileOutputStream fout = new FileOutputStream(filename +".des");
 FileOutputStream digest = new FileOutputStream(filename + ".des.digest");
 // Create the key.
 byte[] desKeyData = password.getBytes( );
 DESKeySpec desKeySpec = new DESKeySpec(desKeyData);
 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
 SecretKey desKey = keyFactory.generateSecret(desKeySpec);
 // Use Data Encryption Standard.
 Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding");
 des.init(Cipher.ENCRYPT_MODE, desKey);
 CipherInputStream cin = new CipherInputStream(fin, des);
 // Use SHA digest algorithm.
 MessageDigest sha = MessageDigest.getInstance("SHA");
 DigestInputStream din = new DigestInputStream(cin, sha);
 byte[] input = new byte[64];
 while (true) {
 int bytesRead = din.read(input);
 if (bytesRead == -1) break;
 fout.write(input, 0, bytesRead);
 }
 digest.write(sha.digest( ));
 digest.close( );
 din.close( );
 fout.flush( );
 fout.close( );
 }
}

The file is read with a file input stream chained to a cipher input stream chained to a digest input stream. As the file is read, encrypted, and digested, it's written into an output file. After the file has been completely read, the digest is written into another file so it can later be compared with the first file. Because the cipher input stream appears before the digest input stream in the chain, the digest is of the ciphertext, not the plaintext. If you read the file with a file input stream chained to a digest input stream chained to a cipher input stream, you would digest the plaintext. In fact, you could even use a file input stream chained to a digest input stream chained to a cipher input stream chained to a second digest input stream to get digests of both plain- and ciphertext.

12.6.2. CipherOutputStream

CipherOutputStream is a subclass of FilterOutputStream.

public class CipherOutputStream extends FilterOutputStream

Each CipherOutputStream object contains a Cipher object used to decrypt or encrypt all data passed as arguments to the write( ) method before writing it to the underlying stream. This Cipher object is set in the constructor. Like all filter stream constructors, this constructor takes another input stream as an argument:

public CipherOutputStream(OutputStream out, Cipher c)

The Cipher object used here must be a properly initialized instance of javax.crypto.Cipher, most likely returned by Cipher.getInstance( ). The Cipher object c should be initialized for encryption or decryption by calling init( ) before being passed to the CipherOutputStream( ) constructor. There is also a protected constructor that might be used by subclasses that want to implement their own, non-JCE-based encryption scheme:

protected CipherOutputStream(OutputStream out)

CipherOutputStream has all the usual methods of any output stream, like write( ), flush( ), and close( ). It overrides five of these methods to do its filtering. Clients use these methods the same way they use them in any output stream. Before the data is written, the stream's cipher either decrypts or encrypts the data. Each of these five methods makes the necessary adjustments to handle encrypted data. For example, the flush( ) method (which is invoked by the close( ) method as well) calls doFinal( ) on the Cipher object to make sure it has finished padding and encrypting all the data before it flushes the final data to the underlying stream.

There are no new methods in CipherOutputStream not declared in the superclass. Anything else you need to do, such as getting the cipher's initialization vector, must be handled by the Cipher object.

Example 12-7 uses CipherOutputStream to decrypt files encrypted by the DigestEncryptor of Example 12-6. A digest input stream chained to a file input stream checks the digest of the ciphertext as it's read from the file. If the digest does not match, an error message is printed. The file is still written into the output file, sincedepending on the algorithm and mode usedit may be partially legible, especially if the error does not occur until relatively late in the encrypted data.

Example 12-7. DigestDecryptor

import java.io.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class DigestDecryptor {
 public static void main(String[] args)
 throws I OExcedption, GeneralSecurityException {
 if (args.length != 3) {
 System.err.println("Usage: java DigestDecryptor infile outfile password");
 return;
 }
 String infile = args[0];
 String outfile = args[1];
 String password = args[2];
 if (password.length( ) < 8 ) {
 System.err.println("Password must be at least eight characters long");
 }
 FileInputStream fin = new FileInputStream(infile);
 FileOutputStream fout = new FileOutputStream(outfile);
 // Get the digest.
 FileInputStream digestIn = new FileInputStream(infile + ".digest");
 DataInputStream dataIn = new DataInputStream(digestIn);
 // SHA digests are always 20 bytes long.
 byte[] oldDigest = new byte[20];
 dataIn.readFully(oldDigest);
 dataIn.close( );
 // Create a key.
 byte[] desKeyData = password.getBytes( );
 DESKeySpec desKeySpec = new DESKeySpec(desKeyData);
 SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
 SecretKey desKey = keyFactory.generateSecret(desKeySpec);
 // Use Data Encryption Standard.
 Cipher des = Cipher.getInstance("DES/ECB/PKCS5Padding");
 des.init(Cipher.DECRYPT_MODE, desKey);
 CipherOutputStream cout = new CipherOutputStream(fout, des);
 // Use SHA digest algorithm.
 MessageDigest sha = MessageDigest.getInstance("SHA");
 DigestInputStream din = new DigestInputStream(fin, sha);
 byte[] input = new byte[64];
 while (true) {
 int bytesRead = din.read(input);
 if (bytesRead == -1) break;
 cout.write(input, 0, bytesRead);
 }
 byte[] newDigest = sha.digest( );
 if (!MessageDigest.isEqual(newDigest, oldDigest)) {
 System.out.println("Input file appears to be corrupt!");
 }
 din.close( );
 cout.flush( );
 cout.close( );
 }
}

Basic I/O

Introducing I/O

Output Streams

Input Streams

Data Sources

File Streams

Network Streams

Filter Streams

Filter Streams

Print Streams

Data Streams

Streams in Memory

Compressing Streams

JAR Archives

Cryptographic Streams

Object Serialization

New I/O

Buffers

Channels

Nonblocking I/O

The File System

Working with Files

File Dialogs and Choosers

Text

Character Sets and Unicode

Readers and Writers

Formatted I/O with java.text

Devices

The Java Communications API

USB

The J2ME Generic Connection Framework

Bluetooth

Character Sets



Java I/O
Java I/O
ISBN: 0596527500
EAN: 2147483647
Year: 2004
Pages: 244

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net