org.gity.internal.crypto.GCMCopy.java Source code

Java tutorial

Introduction

Here is the source code for org.gity.internal.crypto.GCMCopy.java

Source

/* 
 * Copyright (c) 2016, Serphentas
 * All rights reserved.
 *
 * This work is licensed under the Creative Commons Attribution-ShareAlike 4.0
 * International License. To view a copy of this license, visit
 * http://creativecommons.org/licenses/by-sa/4.0/ or send a letter
 * to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.
 */
package org.gity.internal.crypto;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
import org.bouncycastle.crypto.generators.SCrypt;
import org.bouncycastle.util.encoders.Hex;
import org.gity.internal.network.NTP;

/**
 * General purpose class used for encryption and decryption of files, using
 * AES-256 in GCM mode of operation.
 * <p>
 * <b><i>This class is not thread-safe.</i></b>
 *
 * @author Serphentas
 */
public final class GCMCopy {

    private static final String CIPHER = "AES/GCM/NoPadding", CRYPTO_PROVIDER = "BC";
    private static final int CIPHER_KEY_BITS = 256, GCM_NONCE_BYTES = 12, GCM_TAG_BITS = 128, S_BYTES = 64,
            R_BYTES = 64, BUFFER_SIZE = 512, KDF_r = 8, KDF_p = 1, VS1 = S_BYTES, S1N1 = VS1 + GCM_NONCE_BYTES,
            N1K1N = S1N1 + 1, K1NR = N1K1N + R_BYTES + GCM_TAG_BITS / 8, RS2 = K1NR + S_BYTES,
            S2N2 = RS2 + GCM_NONCE_BYTES, N2K2N = S2N2 + 1;
    private static int K1_KDF_N = 19, K2_KDF_N = 19;
    private static final byte[] buf = new byte[BUFFER_SIZE];

    private final Cipher cipher;

    /**
     * Sets the new CPU/RAM cost parameter for scrypt when deriving K1, read as
     * a power of two
     * <p>
     * Default value: 20
     *
     * @param N new CPU/RAM cost for scrypt, as a power of two
     */
    protected static void setK1N(int N) {
        GCMCopy.K1_KDF_N = N;
    }

    /**
     * Sets the new CPU/RAM cost parameter for scrypt when deriving K2, read as
     * a power of two
     * <p>
     * Default value: 19
     *
     * @param N new CPU/RAM cost for scrypt, as a power of two
     */
    protected static void setK2N(int N) {
        GCMCopy.K2_KDF_N = N;
    }

    /**
     * Instantiates a Cipher object using AES-256 in GCM mode of operation,
     * allowing subsequent use for file encryption and decryption
     */
    public GCMCopy() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {
        /*
        suppresses the restriction over keys larger than 128 bits due to the
        JCE Unlimited Strength Jurisdiction Policy
        see http://v.gd/HN1qpB
         */
        Field field = Class.forName("javax.crypto.JceSecurity").getDeclaredField("isRestricted");
        field.setAccessible(true);
        field.set(null, java.lang.Boolean.FALSE);

        // instantiating AES-256 w/ GCM from Bouncy Castle
        this.cipher = Cipher.getInstance(CIPHER, CRYPTO_PROVIDER);
    }

    /**
     * Encrypts a given file with AES-256 in GCM mode of operation
     * <p>
     * Reads data from the InputStream and writes the encrypted data to the
     * OutputStream
     *
     * @param inputFile
     * @param outputFile
     * @throws java.io.IOException
     * @throws java.security.InvalidKeyException
     * @throws java.security.InvalidAlgorithmParameterException
     * @throws javax.crypto.BadPaddingException
     * @throws javax.crypto.IllegalBlockSizeException
     */
    public void encrypt_V00(File inputFile, File outputFile) throws IOException, InvalidKeyException,
            InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
        // defining I/O streams
        InputStream input = new FileInputStream(inputFile);
        OutputStream output = new FileOutputStream(outputFile);

        // getting the encryption password
        char[] pass = DefaultCipher.getEncryptionPassword();

        long fileSize = inputFile.length();

        // generating Sx, Nx, R and Kx
        final byte[] S1 = GPCrypto.randomGen(S_BYTES), S2 = GPCrypto.randomGen(S_BYTES),
                epoch = DatatypeConverter.parseHexBinary(Long.toHexString(NTP.getTime() / 1000)),
                N1 = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, epoch[0], epoch[1], epoch[2],
                        epoch[3] },
                N2 = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, epoch[0], epoch[1], epoch[2],
                        (byte) (epoch[3] + 0x01) },
                R = GPCrypto.randomGen(R_BYTES);
        final SecretKey K1 = new SecretKeySpec(SCrypt.generate(GPCrypto.charToByte(pass), S1,
                (int) Math.pow(2, K1_KDF_N), KDF_r, KDF_p, CIPHER_KEY_BITS / 8), 0, CIPHER_KEY_BITS / 8, "AES"),
                K2 = new SecretKeySpec(
                        SCrypt.generate(R, S2, (int) Math.pow(2, K2_KDF_N), KDF_r, KDF_p, CIPHER_KEY_BITS / 8), 0,
                        CIPHER_KEY_BITS / 8, "AES");

        // writing header
        this.cipher.init(Cipher.ENCRYPT_MODE, K1, new GCMParameterSpec(GCM_TAG_BITS, N1, 0, GCM_NONCE_BYTES));
        output.write((byte) 0x00);
        output.write(S1);
        output.write(N1);
        output.write(DatatypeConverter.parseHexBinary(Integer.toHexString(K1_KDF_N)));
        output.write(cipher.doFinal(R));
        output.write(S2);
        output.write(N2);
        output.write(DatatypeConverter.parseHexBinary(Integer.toHexString(K2_KDF_N)));

        // encrypting file
        this.cipher.init(Cipher.ENCRYPT_MODE, K2, new GCMParameterSpec(GCM_TAG_BITS, N2, 0, GCM_NONCE_BYTES));
        int r = 0, counter = 0;

        while ((r = input.read(buf)) != -1) {
            counter++;
            if (r == BUFFER_SIZE) {
                if (counter * BUFFER_SIZE != fileSize) {
                    output.write(this.cipher.update(buf));
                } else {
                    output.write(this.cipher.doFinal(buf));
                }
            } else {
                output.write(this.cipher.doFinal(Arrays.copyOfRange(buf, 0, r)));
            }
        }

        // erasing cryptographic parameters and closing streams
        GPCrypto.eraseByteArrays(S1, S2, epoch, N1, N2, R);
        GPCrypto.sanitize(buf, 1000);
        GPCrypto.eraseKeys(K1, K2);
        GPCrypto.sanitize(pass);
        input.close();
        output.close();
    }

    /**
     * Decrypts a given file with AES-256 in GCM mode of operation
     *
     * @param inputFile
     * @param outputFile
     * @throws java.io.IOException
     * @throws java.security.InvalidKeyException
     * @throws java.security.InvalidAlgorithmParameterException
     * @throws javax.crypto.BadPaddingException
     * @throws javax.crypto.IllegalBlockSizeException
     */
    public void decrypt_V00(File inputFile, File outputFile) throws IOException, InvalidKeyException,
            InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
        // defining I/O streams
        OutputStream output = new FileOutputStream(outputFile);
        InputStream input = new FileInputStream(inputFile);

        // getting the encryption password
        char[] pass = DefaultCipher.getEncryptionPassword();

        long fileSize = inputFile.length(), iterCnt = fileSize / BUFFER_SIZE, percentage = fileSize / 100L,
                bytesRead = 0L;

        // skipping version byte and reading header
        input.skip(1);
        byte[] header = new byte[234];
        input.read(header, 0, 234);

        // reading Sx, Nx, scrypt factors and generating K1
        final byte[] S1 = Arrays.copyOfRange(header, 0, VS1), N1 = Arrays.copyOfRange(header, VS1, S1N1),
                K1_N = Arrays.copyOfRange(header, S1N1, N1K1N), S2 = Arrays.copyOfRange(header, K1NR, RS2),
                N2 = Arrays.copyOfRange(header, RS2, S2N2), K2_N = Arrays.copyOfRange(header, S2N2, N2K2N);
        final int K1_N_bak = (int) Math.pow(2, Integer.valueOf(Hex.toHexString(K1_N), 16)),
                K2_N_bak = (int) Math.pow(2, Integer.valueOf(Hex.toHexString(K2_N), 16));
        final SecretKey K1 = new SecretKeySpec(
                SCrypt.generate(GPCrypto.charToByte(pass), S1, K1_N_bak, KDF_r, KDF_p, CIPHER_KEY_BITS / 8), 0,
                CIPHER_KEY_BITS / 8, "AES");

        // reading E(K1, N1, R) 
        this.cipher.init(Cipher.DECRYPT_MODE, K1, new GCMParameterSpec(GCM_TAG_BITS, N1, 0, GCM_NONCE_BYTES));
        final byte[] R = cipher.doFinal(Arrays.copyOfRange(header, N1K1N, K1NR));

        // generating K2
        final SecretKey K2 = new SecretKeySpec(SCrypt.generate(R, S2, K2_N_bak, KDF_r, KDF_p, CIPHER_KEY_BITS / 8),
                0, CIPHER_KEY_BITS / 8, "AES");

        // decrypting file
        this.cipher.init(Cipher.DECRYPT_MODE, K2, new GCMParameterSpec(GCM_TAG_BITS, N2, 0, GCM_NONCE_BYTES));
        int r = 0, counter = 0;

        while ((r = input.read(buf)) != -1) {
            counter++;
            if (r == BUFFER_SIZE) {
                if (counter * BUFFER_SIZE != fileSize) {
                    output.write(this.cipher.update(buf));
                } else {
                    output.write(this.cipher.doFinal(buf));
                }
            } else {
                output.write(this.cipher.doFinal(Arrays.copyOfRange(buf, 0, r)));
            }
        }

        // erasing cryptographic parameters and closing streams
        GPCrypto.eraseByteArrays(header, S1, S2, N1, N2, R);
        GPCrypto.sanitize(buf, 1000);
        GPCrypto.eraseKeys(K1, K2);
        GPCrypto.sanitize(pass);
        output.close();
    }
}