ch.lamacrypt.internal.crypto.CPCipher.java Source code

Java tutorial

Introduction

Here is the source code for ch.lamacrypt.internal.crypto.CPCipher.java

Source

/* 
 * Copyright (c) 2000-2016 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
    
 */
package ch.lamacrypt.internal.crypto;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.bouncycastle.crypto.engines.ChaChaEngine;
import org.bouncycastle.crypto.generators.Poly1305KeyGenerator;
import org.bouncycastle.crypto.macs.Poly1305;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.crypto.tls.AlertDescription;
import org.bouncycastle.crypto.tls.TlsFatalAlert;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;

/**
 * General purpose class used for encryption and decryption of files, using
 * ChaCha20 w/ Poly1305 as MAC in encrypt-then-MAC scheme
 * <p>
 * Based on the ChaCha20Poly1305 class from Bouncy Castle but without the TLS
 * related code
 * <p>
 * This class' code has not been verified against any test vector and so it
 * should not be used for actual encryption yet
 *
 * @author LamaGuy
 */
public final class CPCipher {

    private final ChaChaEngine cipher;
    private final Poly1305 mac;
    private static int BUFFER_SIZE = 8192;

    public CPCipher() throws IOException {
        this.cipher = new ChaChaEngine();
        this.mac = new Poly1305();
    }

    public static void setBufferSize(int newSize) {
        BUFFER_SIZE = newSize;
    }

    private int getPlaintextLimit(int ciphertextLimit) {
        return ciphertextLimit - 16;
    }

    /**
     * Encrypts a given file with ChaCha20 w/ Poly1305 as MAC in the
     * encrypt-then-MAC scheme.
     * <p>
     * After each chunk of 8KiB, a MAC tag is written out. This means the output
     * file size is increased by 16B/8192B = 0.001953125%.
     *
     * @param key
     * @param nonce
     * @param input
     * @param output
     * @throws IOException
     */
    protected void encrypt(byte[] key, byte[] nonce, InputStream input, OutputStream output) throws IOException {
        this.cipher.init(true, new ParametersWithIV(new KeyParameter(key), nonce));
        byte[] ciphertextMac = new byte[16], readBuf = new byte[BUFFER_SIZE], chachaBuf = new byte[BUFFER_SIZE];
        initMAC(cipher);

        int r = 0;
        while ((r = input.read(readBuf)) != -1) {
            cipher.processBytes(readBuf, 0, r, chachaBuf, 0);
            output.write(chachaBuf, 0, r);
            updateMAC(chachaBuf, 0, r);
        }

        mac.doFinal(ciphertextMac, 0);
        output.write(ciphertextMac);
    }

    /**
     * Decrypts a given file with ChaCha20 w/ Poly1305 as MAC in
     * encrypt-then-MAC scheme.
     * <p>
     * Reads data from the InputStream and writes the decrypted data to the
     * OutputStream
     *
     * @param key
     * @param nonce
     * @param input
     * @param output
     * @throws IOException
     */
    protected void decrypt(byte[] key, byte[] nonce, InputStream input, OutputStream output) throws IOException {
        this.cipher.init(false, new ParametersWithIV(new KeyParameter(key), nonce));
        byte[] computedMac = new byte[16], receivedMac = new byte[16], readBuf = new byte[BUFFER_SIZE],
                chachaBuf = new byte[BUFFER_SIZE];
        initMAC(cipher);

        int r = 0;
        while ((r = input.read(readBuf)) != -1) {
            if (r == BUFFER_SIZE) {
                // use C in whole to update the MAC and decrypt
                updateMAC(readBuf, 0, r);
                cipher.processBytes(readBuf, 0, r, chachaBuf, 0);
                output.write(chachaBuf, 0, r);
            } else {
                // use all but the last 16 bytes from C to update the MAC and decrypt
                updateMAC(Arrays.copyOfRange(readBuf, 0, r - 16), 0, r - 16);
                cipher.processBytes(Arrays.copyOfRange(readBuf, 0, r - 16), 0, r - 16, chachaBuf, 0);
                output.write(chachaBuf, 0, r - 16);

                // copy the last 16 bytes as the original MAC
                receivedMac = Arrays.copyOfRange(readBuf, r - 16, r);
            }
        }

        // check if the two MACs match
        mac.doFinal(computedMac, 0);
        if (!Arrays.constantTimeAreEqual(computedMac, receivedMac)) {
            throw new TlsFatalAlert(AlertDescription.bad_record_mac);
        }
    }

    /**
     * Initializes Poly1305 with the given instance of ChaCha20Engine
     *
     * @param cipher
     */
    private void initMAC(ChaChaEngine cipher) {
        byte[] firstBlock = new byte[64];
        cipher.processBytes(firstBlock, 0, firstBlock.length, firstBlock, 0);

        // NOTE: The BC implementation puts 'r' after 'k'
        System.arraycopy(firstBlock, 0, firstBlock, 32, 16);
        KeyParameter macKey = new KeyParameter(firstBlock, 16, 32);
        Poly1305KeyGenerator.clamp(macKey.getKey());
        mac.init(macKey);
    }

    /**
     * Updates the state of Poly1305
     *
     * @param buf array containing the input
     * @param off the index in the array the data begins at
     * @param len length of the input starting at off
     */
    private void updateMAC(byte[] buf, int off, int len) {
        mac.update(buf, off, len);

        byte[] longLen = Pack.longToLittleEndian(len & 0xFFFFFFFFL);
        mac.update(longLen, 0, longLen.length);
    }
}