co.lqnt.lockbox.EncryptionCipher.java Source code

Java tutorial

Introduction

Here is the source code for co.lqnt.lockbox.EncryptionCipher.java

Source

/*
 * This file is part of the Lockbox package.
 *
 * Copyright  2013 Erin Millard
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

package co.lqnt.lockbox;

import co.lqnt.lockbox.util.codec.Base64UriCodec;
import co.lqnt.lockbox.util.codec.CodecInterface;
import co.lqnt.lockbox.key.PrivateKeyInterface;
import co.lqnt.lockbox.key.PublicKeyInterface;
import co.lqnt.lockbox.util.SecureRandom;
import co.lqnt.lockbox.util.SecureRandomInterface;
import java.nio.charset.Charset;
import java.util.Arrays;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.encodings.OAEPEncoding;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

/**
 * The standard Lockbox encryption cipher.
 */
public class EncryptionCipher implements EncryptionCipherInterface {
    /**
     * Construct a new encryption cipher.
     */
    public EncryptionCipher() {
        this.base64UriCodec = new Base64UriCodec();
        this.rsaCipher = new OAEPEncoding(new RSAEngine(), new SHA1Digest());
        this.aesCipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESEngine()), new PKCS7Padding());
        this.sha1Digest = new SHA1Digest();
        this.random = new SecureRandom();
        this.asciiCharset = Charset.forName("US-ASCII");
    }

    /**
     * Construct a new encryption cipher.
     *
     * @param base64UriCodec The URI-safe Base64 codec to use.
     * @param rsaCipher      The Bouncy Castle RSA cipher to use.
     * @param aesCipher      The Bouncy Castle AES cipher to use.
     * @param sha1Digest     The Bouncy Castle SHA-1 message digest to use.
     * @param random         The secure random generator to use.
     */
    public EncryptionCipher(CodecInterface base64UriCodec, AsymmetricBlockCipher rsaCipher,
            BufferedBlockCipher aesCipher, Digest sha1Digest, SecureRandomInterface random) {
        this.base64UriCodec = base64UriCodec;
        this.rsaCipher = rsaCipher;
        this.aesCipher = aesCipher;
        this.sha1Digest = sha1Digest;
        this.random = random;
        this.asciiCharset = Charset.forName("US-ASCII");
    }

    /**
     * Get the URI-safe Base64 codec.
     *
     * @return The URI-safe Base64 codec.
     */
    public CodecInterface base64UriCodec() {
        return this.base64UriCodec;
    }

    /**
     * Get the Bouncy Castle RSA cipher.
     *
     * @return The Bouncy Castle RSA cipher.
     */
    public AsymmetricBlockCipher rsaCipher() {
        return this.rsaCipher;
    }

    /**
     * Get the Bouncy Castle AES cipher.
     *
     * @return The Bouncy Castle AES cipher.
     */
    public BufferedBlockCipher aesCipher() {
        return this.aesCipher;
    }

    /**
     * Get the Bouncy Castle SHA-1 message digest.
     *
     * @return The Bouncy Castle SHA-1 message digest.
     */
    public Digest sha1Digest() {
        return this.sha1Digest;
    }

    /**
     * Get the secure random generator.
     *
     * @return The secure random generator.
     */
    public SecureRandomInterface random() {
        return this.random;
    }

    /**
     * Encrypt a data packet.
     *
     * @param key  They key to encrypt with.
     * @param data The data to encrypt.
     *
     * @return The encrypted data.
     */
    public byte[] encrypt(final PublicKeyInterface key, final byte[] data) {
        byte[] generatedKey = this.random().generate(32);
        byte[] iv = this.random().generate(16);

        byte[] keyAndIv = new byte[48];
        System.arraycopy(generatedKey, 0, keyAndIv, 0, 32);
        System.arraycopy(iv, 0, keyAndIv, 32, 16);

        this.rsaCipher().init(true, key.bcKeyParameters());

        byte[] encryptedKeyAndIv;
        try {
            encryptedKeyAndIv = this.rsaCipher().processBlock(keyAndIv, 0, 48);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }

        byte[] hash = new byte[20];
        this.sha1Digest().reset();
        this.sha1Digest().update(data, 0, data.length);
        this.sha1Digest().doFinal(hash, 0);

        byte[] dataAndHash = new byte[20 + data.length];
        System.arraycopy(data, 0, dataAndHash, 0, data.length);
        System.arraycopy(hash, 0, dataAndHash, data.length, 20);

        byte[] encryptedData = this.encryptAes(generatedKey, iv, dataAndHash);

        int encryptedSize = encryptedKeyAndIv.length + encryptedData.length;
        byte[] encrypted = new byte[encryptedSize];
        System.arraycopy(encryptedKeyAndIv, 0, encrypted, 0, encryptedKeyAndIv.length);
        System.arraycopy(encryptedData, 0, encrypted, encryptedKeyAndIv.length, encryptedData.length);

        return this.base64UriCodec().encode(encrypted);
    }

    /**
     * Encrypt a data packet.
     *
     * @param key  They key to encrypt with.
     * @param data The data to encrypt.
     *
     * @return The encrypted data.
     */
    public String encrypt(final PublicKeyInterface key, final String data) {
        return new String(this.encrypt(key, data.getBytes(this.asciiCharset)), this.asciiCharset);
    }

    /**
     * Encrypt a data packet.
     *
     * @param key  They key to encrypt with.
     * @param data The data to encrypt.
     *
     * @return The encrypted data.
     */
    public byte[] encrypt(final PrivateKeyInterface key, final byte[] data) {
        return this.encrypt(key.publicKey(), data);
    }

    /**
     * Encrypt a data packet.
     *
     * @param key  They key to encrypt with.
     * @param data The data to encrypt.
     *
     * @return The encrypted data.
     */
    public String encrypt(final PrivateKeyInterface key, final String data) {
        return new String(this.encrypt(key, data.getBytes(this.asciiCharset)), this.asciiCharset);
    }

    /**
     * Encrypt some data with AES and PKCS #7 padding.
     *
     * @param key  The key to use.
     * @param iv   The initialization vector to use.
     * @param data The data to encrypt.
     *
     * @return The decrypted data.
     */
    protected byte[] encryptAes(final byte[] key, final byte[] iv, final byte[] data) {
        CipherParameters parameters = new ParametersWithIV(new KeyParameter(key), iv);

        this.aesCipher().reset();
        this.aesCipher().init(true, parameters);

        int outputSize = this.aesCipher().getOutputSize(data.length);
        byte[] encrypted = new byte[outputSize];

        int length = this.aesCipher().processBytes(data, 0, data.length, encrypted, 0);

        try {
            length += this.aesCipher().doFinal(encrypted, length);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        } catch (DataLengthException e) {
            throw new RuntimeException(e);
        }

        return Arrays.copyOfRange(encrypted, 0, length);
    }

    private CodecInterface base64UriCodec;
    private AsymmetricBlockCipher rsaCipher;
    private BufferedBlockCipher aesCipher;
    private Digest sha1Digest;
    private SecureRandomInterface random;
    private Charset asciiCharset;
}