org.crypto.sse.CryptoPrimitives.java Source code

Java tutorial

Introduction

Here is the source code for org.crypto.sse.CryptoPrimitives.java

Source

/** * Copyright (C) 2016 Tarik Moataz
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//***********************************************************************************************//

// This file contains most cryptographic primitives : AES in CTR mode for file and string encryption, 
// AES with synthetic IV, HMAC, CMAC-AES and HCB1 online cipher. The file also contains some other
// tools for bytes manipulation and some variations of hash functions to be user
//***********************************************************************************************//

package org.crypto.sse;

import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.engines.AESFastEngine;
import org.bouncycastle.crypto.macs.CMac;
import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.prng.ThreadedSeedGenerator;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;

public class CryptoPrimitives {

    private CryptoPrimitives() {
    }

    // ***********************************************************************************************//

    ///////////////////// KeyGen return a raw key based on PBE
    ///////////////////// PKCS12/////////////////////////////
    ///////////////////// mostly taken from
    ///////////////////// org.bouncycastle.jce.provider.test.PBETest
    // check also doc in
    ///////////////////// http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SecretKeyFactory/////////////////////////////
    // ***********************************************************************************************//

    public static byte[] keyGenSetM(String pass, byte[] salt, int icount, int keySize)
            throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException {

        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        KeySpec spec = new PBEKeySpec(pass.toCharArray(), salt, icount, keySize);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
        return secret.getEncoded();

    }

    // ***********************************************************************************************//

    ///////////////////// CMAC-AES generation /////////////////////////////

    // ***********************************************************************************************//

    public static byte[] generateCmac(byte[] key, String msg) throws UnsupportedEncodingException {
        CMac cmac = new CMac(new AESFastEngine());
        byte[] data = msg.getBytes("UTF-8");
        byte[] output = new byte[cmac.getMacSize()];

        cmac.init(new KeyParameter(key));
        cmac.reset();
        cmac.update(data, 0, data.length);
        cmac.doFinal(output, 0);
        return output;
    }

    // ***********************************************************************************************//

    ///////////////////// HMAC-SHA256 generation /////////////////////////////

    // ***********************************************************************************************//

    public static byte[] generateHmac(byte[] key, String msg) throws UnsupportedEncodingException {

        HMac hmac = new HMac(new SHA256Digest());
        byte[] result = new byte[hmac.getMacSize()];
        byte[] msgAry = msg.getBytes("UTF-8");
        hmac.init(new KeyParameter(key));
        hmac.reset();
        hmac.update(msgAry, 0, msgAry.length);
        hmac.doFinal(result, 0);
        return result;
    }

    // ***********************************************************************************************//

    ///////////////////// HMAC-SHA256 generation (Byte[] input instead of a
    ///////////////////// String)/////////////////////////////

    // ***********************************************************************************************//

    public static byte[] generateHmac(byte[] key, byte[] msg) throws UnsupportedEncodingException {

        HMac hmac = new HMac(new SHA256Digest());
        byte[] result = new byte[hmac.getMacSize()];
        hmac.init(new KeyParameter(key));
        hmac.reset();
        hmac.update(msg, 0, msg.length);
        hmac.doFinal(result, 0);
        return result;
    }

    // ***********************************************************************************************//

    ///////////////////// HMAC-SHA512generation /////////////////////////////

    // ***********************************************************************************************//

    public static byte[] generateHmac512(byte[] key, String msg) throws UnsupportedEncodingException {

        HMac hmac = new HMac(new SHA512Digest());
        byte[] result = new byte[hmac.getMacSize()];
        byte[] msgAry = msg.getBytes("UTF-8");
        hmac.init(new KeyParameter(key));
        hmac.reset();
        hmac.update(msgAry, 0, msgAry.length);
        hmac.doFinal(result, 0);
        return result;
    }

    // ***********************************************************************************************//

    ///////////////////// Salt generation/RandomBytes: it is generated just once
    ///////////////////// and it is not necessary to keep it secret
    ///////////////////// /////////////////////////////

    // can also be used for random bit generation
    // ***********************************************************************************************//

    public static byte[] randomBytes(int sizeOfSalt) {
        byte[] salt = new byte[sizeOfSalt];
        ThreadedSeedGenerator thread = new ThreadedSeedGenerator();
        SecureRandom random = new SecureRandom();
        random.setSeed(thread.generateSeed(20, true));
        random.nextBytes(salt);
        return salt;
    }

    // ***********************************************************************************************//

    ///////////////////// Message authentication+Encryption (Authenticated encryption)
    ///////////////////// /////////////////////////////

    // ***********************************************************************************************//   

    public static byte[][] auth_encrypt_AES_HMAC(byte[] keyEnc, byte[] keyHMAC, byte[] ivBytes, String identifier,
            int maxSize) throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, IOException {

        byte[][] output = new byte[2][];

        output[1] = encryptAES_CTR_String(keyEnc, ivBytes, identifier, maxSize);
        output[0] = generateHmac(keyHMAC, output[1]);

        return output;
    }

    // ***********************************************************************************************//

    ///////////////////// Message authentication+Decryption (Authenticated encryption)
    ///////////////////// /////////////////////////////

    // ***********************************************************************************************//   

    public static byte[][] auth_decrypt_AES_HMAC(byte[] keyEnc, byte[] keyHMAC, byte[][] input)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, IOException {

        byte[][] output = new byte[2][1];
        output[0][0] = '0';
        output[1] = decryptAES_CTR_String(input[1], keyEnc);
        // splitting required for correctness, we can get rid of the delimiter in encryptAES_CTR_String
        // if we use the same string length

        byte[] hmacResult = generateHmac(keyHMAC, input[1]);

        if (Arrays.equals(hmacResult, input[0])) {
            output[0][0] = '1';
        }

        return output;
    }

    // ***********************************************************************************************//

    ///////////////////// AES-CTR encryption of a String
    ///////////////////// /////////////////////////////

    // ***********************************************************************************************//

    public static byte[] encryptAES_CTR_String(byte[] keyBytes, byte[] ivBytes, String identifier,
            int sizeOfFileName) throws InvalidKeyException, InvalidAlgorithmParameterException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, IOException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        // Concatenate the title with the text. The title should be at most
        // "sizeOfFileName" characters including 3 characters marking the end of
        // it
        identifier = identifier + "\t\t\t";
        byte[] input = concat(identifier.getBytes(), new byte[sizeOfFileName - identifier.getBytes().length]);

        // byte[] input = concat(input1 , input0);
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        ByteArrayInputStream bIn = new ByteArrayInputStream(input);
        CipherInputStream cIn = new CipherInputStream(bIn, cipher);
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        int ch;
        while ((ch = cIn.read()) >= 0) {
            bOut.write(ch);
        }
        byte[] cipherText = concat(ivBytes, bOut.toByteArray());

        return cipherText;

    }

    // ***********************************************************************************************//

    ///////////////////// AES-CTR Decryption of String
    ///////////////////// /////////////////////////////

    // ***********************************************************************************************//

    public static byte[] decryptAES_CTR_String(byte[] input, byte[] keyBytes)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, IOException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        // byte[] input = readAlternateImpl(folder + fileName);

        byte[] ivBytes = new byte[16];

        byte[] cipherText = new byte[input.length - 16];

        System.arraycopy(input, 0, ivBytes, 0, ivBytes.length);
        System.arraycopy(input, ivBytes.length, cipherText, 0, cipherText.length);

        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");

        // Initalization of the Cipher
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        CipherOutputStream cOut = new CipherOutputStream(bOut, cipher);

        cOut.write(cipherText);
        cOut.close();

        return bOut.toByteArray();
    }

    // ***********************************************************************************************//

    ///////////////////// AES-CTR encryption of a String
    ///////////////////// /////////////////////////////

    // ***********************************************************************************************//

    public static byte[] DTE_encryptAES_CTR_String(byte[] encKeyBytes, byte[] PRFKeyBytes, String identifier,
            int sizeOfFileName) throws InvalidKeyException, InvalidAlgorithmParameterException,
            NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, IOException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        // Title Encoding: Concatenate the title with the text. The title should
        // be at most "sizeOfFileName" characters including 3 characters marking
        // the end of it
        identifier = identifier + "\t\t\t";
        byte[] input = concat(identifier.getBytes(), new byte[sizeOfFileName - identifier.getBytes().length]);

        // Synthetic IV

        byte[] ivBytes = CryptoPrimitives.generateCmac(PRFKeyBytes, identifier);

        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec key = new SecretKeySpec(encKeyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        ByteArrayInputStream bIn = new ByteArrayInputStream(input);
        CipherInputStream cIn = new CipherInputStream(bIn, cipher);
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        int ch;
        while ((ch = cIn.read()) >= 0) {
            bOut.write(ch);
        }
        byte[] cipherText = concat(ivBytes, bOut.toByteArray());

        return cipherText;

    }

    // ***********************************************************************************************//

    ///////////////////// AES-CTR encryption (Designed for sending encrypted
    ///////////////////// files directly to the outsourced
    ///////////////////// servers)/////////////////////////////

    // ***********************************************************************************************//

    public static void encryptAES_CTR_Socket(ObjectOutputStream out, String folderName, String outputFileName,
            String folderInput, String inputFileName, byte[] keyBytes, byte[] ivBytes)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, IOException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        byte[] input0 = readAlternateImpl(folderInput + inputFileName);

        // Concatenate the title with the text. The title should be at most 42
        // characters with 2 characters marking the end of it

        String endOfTitle = "\t";

        inputFileName = inputFileName + endOfTitle;
        byte[] input1 = concat(inputFileName.getBytes(), new byte[42 - inputFileName.getBytes().length]);

        byte[] input = concat(input1, input0);
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        ByteArrayInputStream bIn = new ByteArrayInputStream(input);
        CipherInputStream cIn = new CipherInputStream(bIn, cipher);
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        int ch;
        while ((ch = cIn.read()) >= 0) {
            bOut.write(ch);
        }

        byte[] cipherText = concat(ivBytes, bOut.toByteArray());

        // Send the outputfile name
        out.writeObject(outputFileName);
        out.flush();

        // Send the ciphertext
        out.writeObject(cipherText);
        out.flush();

    }

    // ***********************************************************************************************//

    ///////////////////// AES-CTR encryption /////////////////////////////

    // ***********************************************************************************************//

    public static void encryptAES_CTR(String folderName, String outputFileName, String folderInput,
            String inputFileName, byte[] keyBytes, byte[] ivBytes)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, IOException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        byte[] input0 = readAlternateImpl(folderInput + inputFileName);

        // Concatenate the title with the text. The title should be at most 42
        // characters with 2 characters marking the end of it

        String endOfTitle = "\t";

        inputFileName = inputFileName + endOfTitle;
        byte[] input1 = concat(inputFileName.getBytes(), new byte[42 - inputFileName.getBytes().length]);

        byte[] input = concat(input1, input0);
        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
        ByteArrayInputStream bIn = new ByteArrayInputStream(input);
        CipherInputStream cIn = new CipherInputStream(bIn, cipher);
        ByteArrayOutputStream bOut = new ByteArrayOutputStream();

        int ch;
        while ((ch = cIn.read()) >= 0) {
            bOut.write(ch);
        }

        byte[] cipherText = concat(ivBytes, bOut.toByteArray());

        write(cipherText, outputFileName, folderName);

    }

    // ***********************************************************************************************//

    ///////////////////// AES-CTR Decryption /////////////////////////////

    // ***********************************************************************************************//

    public static void decryptAES_CTR(String folderOUT, byte[] input, byte[] keyBytes)
            throws InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException, IOException {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        byte[] ivBytes = new byte[16];
        byte[] cipherText = new byte[input.length - 16];

        System.arraycopy(input, 0, ivBytes, 0, ivBytes.length);
        System.arraycopy(input, ivBytes.length, cipherText, 0, cipherText.length);

        IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
        SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding", "BC");

        // Initalization of the Cipher
        cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

        ByteArrayOutputStream bOut = new ByteArrayOutputStream();
        CipherOutputStream cOut = new CipherOutputStream(bOut, cipher);

        cOut.write(cipherText);
        cOut.close();

        // Splitting the title from the plaintext

        byte[] title = new byte[42];
        byte[] plaintext = new byte[bOut.toByteArray().length - 42];
        System.arraycopy(bOut.toByteArray(), 0, title, 0, title.length);
        System.arraycopy(bOut.toByteArray(), title.length, plaintext, 0, plaintext.length);

        String filename = new String(title).split("\t")[0];

        write(plaintext, filename, folderOUT);
    }

    // ***********************************************************************************************//

    ///////////////////// Generic Read and Write Byte to files
    ///////////////////// /////////////////////////////

    // ***********************************************************************************************//

    public static void write(byte[] aInput, String aOutputFileName, String dirName) {
        // creation of a directory if it is not created
        // sanitizing the aOutputFileName

        (new File(dirName)).mkdir();
        try {
            OutputStream output = null;
            try {
                output = new BufferedOutputStream(new FileOutputStream(dirName + "/" + aOutputFileName));
                output.write(aInput);
            } finally {
                output.close();
            }
        } catch (FileNotFoundException ex) {
            System.out.println("File not found.");
        } catch (IOException ex) {
            System.out.println(ex);
        }
    }

    // Read
    public static byte[] readAlternateImpl(String aInputFileName) {
        File file = new File(aInputFileName);
        byte[] result = null;
        try {
            InputStream input = new BufferedInputStream(new FileInputStream(file));
            result = readAndClose(input);
        } catch (FileNotFoundException ex) {
            System.out.println(ex);
        }
        return result;
    }

    // Read
    private static byte[] readAndClose(InputStream aInput) {
        byte[] bucket = new byte[32 * 1024];
        ByteArrayOutputStream result = null;
        try {
            try {

                result = new ByteArrayOutputStream(bucket.length);
                int bytesRead = 0;
                while (bytesRead != -1) {
                    bytesRead = aInput.read(bucket);
                    if (bytesRead > 0) {
                        result.write(bucket, 0, bytesRead);
                    }
                }
            } finally {
                aInput.close();
            }
        } catch (IOException ex) {
            System.out.println(ex);
        }
        return result.toByteArray();
    }

    // ***********************************************************************************************//

    ///////////////////// Transform an array of bytes to an integer based on a
    ///////////////////// specific number of bits needed
    // Note that these functionalities can be further enhanced for better
    ///////////////////// performances /////////////////////////////

    // ***********************************************************************************************//

    public static int getBit(byte[] data, int pos) {
        int posByte = pos / 8;
        int posBit = pos % 8;
        byte valByte = data[posByte];
        int valInt = valByte >> (8 - (posBit + 1)) & 0x0001;
        return valInt;
    }

    public static int[] getBits(byte[] data, int numberOfBits) {
        int[] bitArray = new int[numberOfBits];
        for (int j = 0; j < numberOfBits; j++) {
            bitArray[j] = getBit(data, j);
        }
        return bitArray;
    }

    public static int getIntFromByte(byte[] byteArray, int numberOfBits) {
        int result = 0;
        int[] bitArray = getBits(byteArray, numberOfBits);

        for (int i = 0; i < numberOfBits; i++) {
            result = result + (int) bitArray[i] * (int) Math.pow(2, i);
        }
        return result;
    }

    public static long getLongFromByte(byte[] byteArray, int numberOfBits) {
        long result = 0;
        int[] bitArray = getBits(byteArray, numberOfBits);

        for (int i = 0; i < numberOfBits; i++) {
            result = result + bitArray[i] * (int) Math.pow(2, i);
        }
        return result;
    }

    public static boolean[] intToBoolean(int number, int numberOfBits) {
        boolean[] pathNumber = new boolean[numberOfBits];

        // represent the number in a binary vector
        String s = Integer.toString(number, 2);
        String s1 = "";
        for (int i = 0; i < s.length(); i++) {
            s1 = s1 + s.charAt(s.length() - i - 1);
        }

        // pad the binary vector by zeros to have the same length as the number
        // of bits requested
        while (s1.length() < numberOfBits) {
            s1 = s1 + "0";
        }

        // convert the string s to an integer representation of bits (specially
        // in a boolean array)
        for (int i = 0; i < numberOfBits; i++) {
            pathNumber[i] = (s1.charAt(i) != '0');
        }
        return pathNumber;
    }

    public static String booleanToString(boolean[] message) {
        String result = "";
        for (int i = 0; i < message.length; i++) {

            if (message[i] == true) {
                result = result + 1;

            } else {
                result = result + 0;

            }
        }

        return result;
    }

    public static byte[] booleanToBytes(boolean[] input) {
        byte[] byteArray = new byte[input.length / 8];
        for (int entry = 0; entry < byteArray.length; entry++) {
            for (int bit = 0; bit < 8; bit++) {
                if (input[entry * 8 + bit]) {
                    byteArray[entry] |= (128 >> bit);
                }
            }
        }

        return byteArray;
    }

    public static boolean[] bytesToBoolean(byte[] bytes) {
        boolean[] bits = new boolean[bytes.length * 8];
        for (int i = 0; i < bytes.length * 8; i++) {
            if ((bytes[i / 8] & (1 << (7 - (i % 8)))) > 0)
                bits[i] = true;
        }
        return bits;
    }
    // ***********************************************************************************************//

    ///////////////////// byte array concatenation /////////////////////////////

    // ***********************************************************************************************//

    public static byte[] concat(byte[] a, byte[] b) {
        int aLen = a.length;
        int bLen = b.length;
        byte[] c = new byte[aLen + bLen];
        System.arraycopy(a, 0, c, 0, aLen);
        System.arraycopy(b, 0, c, aLen, bLen);
        return c;
    }

    // ***********************************************************************************************//

    ///////////////////// Circular hashing /////////////////////////////

    // ***********************************************************************************************//

    public static boolean[] circular(byte[] key, boolean[] message) throws UnsupportedEncodingException {
        boolean result[] = new boolean[message.length];
        String tmpResult = "";
        String tmpMes = "";

        for (int i = message.length - 1; i >= 0; i--) {

            tmpMes = tmpMes + String.valueOf(message[i]);

            byte[] cmac = CryptoPrimitives.generateCmac(key, tmpResult + tmpMes);
            tmpResult = tmpResult + String.valueOf(CryptoPrimitives.getBit(cmac, 0) != 0);
            result[i] = (CryptoPrimitives.getBit(cmac, 0) != 0);

        }

        return result;
    }

    public static boolean circularXOR(boolean[] message) {
        boolean result = false;
        for (int i = 0; i < message.length; i++) {
            result = result ^ message[i];
        }
        System.out.print(" RESULT \t" + result);
        return result;
    }

    // ***********************************************************************************************//

    ///////////////////// Online Cipher Implementation of HCBC1 of Bellare et
    ///////////////////// al. with AES-CTR block cipher
    //////////////////// and hash function SHA-256
    ///////////////////// (http://eprint.iacr.org/2007/197)/////////////////////////////

    // ***********************************************************************************************//

    public static boolean[][] onlineCipher(byte[] keyHash, byte[] keyEnc, boolean[] plaintext) throws IOException,
            InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {

        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        // Block extension using Block expansion function
        int blockSize = 128;
        boolean[][] blocks = new boolean[plaintext.length][blockSize];

        // le premier block contient le vecteur dntialization
        boolean[][] tmpResults1 = new boolean[plaintext.length + 1][blockSize];
        tmpResults1[0] = new boolean[blockSize];
        ;

        // temporary results 2
        boolean[][] tmpResults2 = new boolean[plaintext.length][blockSize];

        // final results
        boolean[][] results = new boolean[plaintext.length][blockSize];

        for (int i = 0; i < plaintext.length; i++) {
            // we have one bit and we add 127 bits to it
            blocks[i][0] = plaintext[i];
            for (int j = 1; j < blockSize; j++) {
                blocks[i][j] = false;
            }
        }

        SecretKeySpec key = new SecretKeySpec(keyEnc, "AES");

        Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding", "BC");

        cipher.init(Cipher.ENCRYPT_MODE, key);
        for (int i = 0; i < plaintext.length; i++) {
            byte[] hmac = CryptoPrimitives.generateHmac(keyHash, CryptoPrimitives.booleanToString(tmpResults1[i]));

            for (int j = 0; j < blockSize; j++) {
                tmpResults2[i][j] = (CryptoPrimitives.getBit(hmac, j) != 0) ^ blocks[i][j];
            }

            ByteArrayInputStream bIn = new ByteArrayInputStream(CryptoPrimitives.booleanToBytes(tmpResults2[i]));

            CipherInputStream cIn = new CipherInputStream(bIn, cipher);

            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
            int ch;
            while ((ch = cIn.read()) >= 0) {
                bOut.write(ch);
            }
            results[i] = CryptoPrimitives.bytesToBoolean(bOut.toByteArray());
            tmpResults1[i + 1] = results[i];

        }

        return results;

    }

    // ***********************************************************************************************//

    ///////////////////// Enhanced implementation: Online Cipher Implementation
    ///////////////////// of HCBC1 of Bellare et al. with AES-CTR block cipher
    //////////////////// and hash function SHA-256
    ///////////////////// (http://eprint.iacr.org/2007/197)/////////////////////////////

    // ***********************************************************************************************//

    public static byte[][] onlineCipherOWF(byte[] keyHash, byte[] keyHash2, boolean[] plaintext) throws IOException,
            InvalidKeyException, NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException {

        // Block extension using Block expansion function (in bytes)
        int blockSize = 32;
        byte[][] blocks = new byte[plaintext.length][blockSize];

        // le premier block contient le vecteur dntialization
        byte[][] tmpResults1 = new byte[plaintext.length + 1][blockSize];
        tmpResults1[0] = new byte[blockSize];
        ;

        // temporary results 2
        byte[][] tmpResults2 = new byte[plaintext.length][blockSize];

        // final results
        byte[][] results = new byte[plaintext.length][blockSize];

        for (int i = 0; i < plaintext.length; i++) {
            // we have one byte and we pad to 32 bytes
            blocks[i][0] = (byte) (plaintext[i] ? 1 : 0);
            for (int j = 1; j < blockSize; j++) {
                blocks[i][j] = 0;
            }
        }

        for (int i = 0; i < plaintext.length; i++) {

            byte[] hmac = CryptoPrimitives.generateHmac(keyHash, tmpResults1[i]);

            int j = 0;
            for (byte b : hmac)
                tmpResults2[i][j] = (byte) (b ^ blocks[i][j++]);

            results[i] = CryptoPrimitives.generateHmac(keyHash2, tmpResults2[i]);
            tmpResults1[i + 1] = results[i];

        }

        return results;

    }
}