edu.wisc.doit.tcrypt.BouncyCastleFileEncrypter.java Source code

Java tutorial

Introduction

Here is the source code for edu.wisc.doit.tcrypt.BouncyCastleFileEncrypter.java

Source

/**
 * Copyright 2012, Board of Regents of the University of
 * Wisconsin System. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Board of Regents of the University of Wisconsin
 * System licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a
 * copy of the License at:
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package edu.wisc.doit.tcrypt;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.security.PublicKey;
import java.security.SecureRandom;

import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.CloseShieldOutputStream;
import org.apache.commons.io.output.TeeOutputStream;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.GeneralDigest;
import org.bouncycastle.crypto.generators.OpenSSLPBEParametersGenerator;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.io.DigestOutputStream;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

/**
 * Encrypts files using a public key
 * 
 * @author Eric Dalquist
 * @version $Revision: 187 $
 */
public class BouncyCastleFileEncrypter extends AbstractPublicKeyEncrypter implements FileEncrypter {
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
    private static final int PASSWORD_LENGTH = 128;
    private static final int SALT_LENGTH = 8;

    public BouncyCastleFileEncrypter(AsymmetricKeyParameter publicKeyParam) {
        super(publicKeyParam);
    }

    public BouncyCastleFileEncrypter(PublicKey publicKey) throws IOException {
        super(publicKey);
    }

    public BouncyCastleFileEncrypter(Reader publicKeyReader) throws IOException {
        super(publicKeyReader);
    }

    public BouncyCastleFileEncrypter(SubjectPublicKeyInfo publicKey) throws IOException {
        super(publicKey);
    }

    @Override
    public void encrypt(String fileName, int size, InputStream inputStream, OutputStream outputStream)
            throws InvalidCipherTextException, IOException {
        final TarArchiveOutputStream tarArchiveOutputStream = new TarArchiveOutputStream(outputStream, 512,
                ENCODING);

        final BufferedBlockCipher cipher = createCipher(tarArchiveOutputStream);

        startEncryptedFile(fileName, size, tarArchiveOutputStream, cipher);

        //Setup cipher output stream, has to protect from close as the cipher stream must close but the tar cannot get closed yet
        final CipherOutputStream cipherOutputStream = new CipherOutputStream(
                new CloseShieldOutputStream(tarArchiveOutputStream), cipher);

        //Setup digester
        final DigestOutputStream digestOutputStream = new DigestOutputStream(this.createDigester());

        //Perform streaming encryption and hashing of the file
        IOUtils.copy(inputStream, new TeeOutputStream(cipherOutputStream, digestOutputStream));
        cipherOutputStream.close();

        tarArchiveOutputStream.closeArchiveEntry();

        //Capture the hash code of the encrypted file
        digestOutputStream.close();
        final byte[] hashBytes = digestOutputStream.getDigest();

        this.writeHashfile(tarArchiveOutputStream, hashBytes);

        //Close the TAR stream, nothing else should be written to it
        tarArchiveOutputStream.close();
    }

    @Override
    public OutputStream encrypt(String fileName, int size, OutputStream outputStream)
            throws InvalidCipherTextException, IOException, DecoderException {
        final TarArchiveOutputStream tarOutputStream = new TarArchiveOutputStream(outputStream, ENCODING);

        final BufferedBlockCipher cipher = createCipher(tarOutputStream);

        startEncryptedFile(fileName, size, tarOutputStream, cipher);

        //Protect the underlying TAR stream from being closed by the cipher stream
        final CloseShieldOutputStream closeShieldTarStream = new CloseShieldOutputStream(tarOutputStream);

        //Setup the encrypting cipher stream
        final CipherOutputStream chipherStream = new CipherOutputStream(closeShieldTarStream, cipher);

        //Generate a digest of the pre-encryption data
        final GeneralDigest digest = this.createDigester();
        final DigestOutputStream digestOutputStream = new DigestOutputStream(digest);

        //Write data to both the digester and cipher
        final TeeOutputStream teeStream = new TeeOutputStream(digestOutputStream, chipherStream);
        return new EncryptingOutputStream(teeStream, tarOutputStream, digest);
    }

    protected void startEncryptedFile(String fileName, int size,
            final TarArchiveOutputStream tarArchiveOutputStream, final BufferedBlockCipher cipher)
            throws IOException {
        //Add the TAR entry and calculate the output size based on the input size
        final TarArchiveEntry encfileEntry = new TarArchiveEntry(fileName + ENC_SUFFIX);
        final int outputSize = cipher.getOutputSize(size);
        encfileEntry.setSize(outputSize);
        tarArchiveOutputStream.putArchiveEntry(encfileEntry);
    }

    protected BufferedBlockCipher createCipher(final TarArchiveOutputStream tarArchiveOutputStream)
            throws InvalidCipherTextException, IOException {
        //Generate cipher parameters and key file
        final ParametersWithIV cipherParameters = generateParameters();

        //Write out the key file
        this.writeKeyfile(tarArchiveOutputStream, cipherParameters);

        //Create the cipher
        return this.getEncryptBlockCipher(cipherParameters);
    }

    protected ParametersWithIV generateParameters() throws InvalidCipherTextException, IOException {
        //Generate a random password
        final byte[] passwordBytes = new byte[PASSWORD_LENGTH];
        SECURE_RANDOM.nextBytes(passwordBytes);
        final byte[] passwordBase64Bytes = Base64.encodeBase64(passwordBytes);
        final String passwordBase64String = new String(passwordBase64Bytes, CHARSET);

        //Generate a random salt
        final byte[] saltBytes = new byte[SALT_LENGTH];
        SECURE_RANDOM.nextBytes(saltBytes);

        //Generate key & iv
        final OpenSSLPBEParametersGenerator generator = new OpenSSLPBEParametersGenerator();
        generator.init(PBEParametersGenerator.PKCS5PasswordToBytes(passwordBase64String.toCharArray()), saltBytes);
        return (ParametersWithIV) generator.generateDerivedParameters(KEY_LENGTH, IV_LENGTH);
    }

    protected void writeKeyfile(TarArchiveOutputStream tarArchiveOutputStream, ParametersWithIV cipherParameters)
            throws IOException, InvalidCipherTextException {
        final KeyParameter keyParameter = (KeyParameter) cipherParameters.getParameters();

        final byte[] keyBytes = keyParameter.getKey();
        final char[] keyHexChars = Hex.encodeHex(keyBytes);

        final byte[] ivBytes = cipherParameters.getIV();
        final char[] ivHexChars = Hex.encodeHex(ivBytes);

        //Create keyfile contents
        final String keyfileString = new StringBuilder(keyHexChars.length + 1 + ivHexChars.length)
                .append(keyHexChars).append(KEYFILE_LINE_SEPERATOR).append(ivHexChars).toString();

        encryptAndWrite(tarArchiveOutputStream, keyfileString, KEYFILE_ENC_NAME);
    }

    protected void writeHashfile(TarArchiveOutputStream tarArchiveOutputStream, byte[] hashBytes)
            throws IOException, InvalidCipherTextException {
        final String hash = new String(Base64.encodeBase64(hashBytes), FileEncrypter.CHARSET);
        encryptAndWrite(tarArchiveOutputStream, hash, HASHFILE_ENC_NAME);
    }

    /**
     * Encrypts and encodes the string and writes it to the tar output stream with the specified file name
     */
    protected void encryptAndWrite(TarArchiveOutputStream tarArchiveOutputStream, String contents, String fileName)
            throws InvalidCipherTextException, IOException {
        final byte[] contentBytes = contents.getBytes(CHARSET);

        //Encrypt contents
        final AsymmetricBlockCipher encryptCipher = this.getEncryptCipher();
        final byte[] encryptedContentBytes = encryptCipher.processBlock(contentBytes, 0, contentBytes.length);
        final byte[] encryptedContentBase64Bytes = Base64.encodeBase64(encryptedContentBytes);

        //Write encrypted contents to tar output stream
        final TarArchiveEntry contentEntry = new TarArchiveEntry(fileName);
        contentEntry.setSize(encryptedContentBase64Bytes.length);
        tarArchiveOutputStream.putArchiveEntry(contentEntry);
        tarArchiveOutputStream.write(encryptedContentBase64Bytes);
        tarArchiveOutputStream.closeArchiveEntry();
    }

    private final class EncryptingOutputStream extends FilterOutputStream {
        private final TarArchiveOutputStream tarOutputStream;
        private final Digest digest;

        private EncryptingOutputStream(OutputStream is, TarArchiveOutputStream tarOutputStream, Digest digest) {
            super(is);

            this.tarOutputStream = tarOutputStream;
            this.digest = digest;
        }

        @Override
        public void close() throws IOException {
            //Complete the encryption
            super.close();

            //Close the tar entry
            tarOutputStream.closeArchiveEntry();

            //Write the hash of the plain file
            byte[] hashBytes = new byte[digest.getDigestSize()];
            digest.doFinal(hashBytes, 0);
            try {
                writeHashfile(tarOutputStream, hashBytes);
            } catch (InvalidCipherTextException e) {
                throw new IOException(e);
            }

            //Close the TAR stream, nothing else should be written to it
            tarOutputStream.close();
        }
    }
}