org.syncany.tests.crypto.AesGcmWithBcInputStreamTest.java Source code

Java tutorial

Introduction

Here is the source code for org.syncany.tests.crypto.AesGcmWithBcInputStreamTest.java

Source

/*
 * Syncany, www.syncany.org
 * Copyright (C) 2011-2014 Philipp C. Heckel <philipp.heckel@gmail.com> 
 *
 * 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/>.
 */
package org.syncany.tests.crypto;

import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;

import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.io.InvalidCipherTextIOException;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import org.junit.Test;
import org.syncany.config.Logging;

/**
 * Tests to test the Bouncy Castle implementation of the CipherInputStream
 * 
 * @see https://github.com/binwiederhier/cipherinputstream-aes-gcm/blob/e9759ca71557e5d1da26ae72f6ce5aac918e34b0/src/CipherInputStreamIssuesTests.java
 * @see http://blog.philippheckel.com/2014/03/01/cipherinputstream-for-aead-modes-is-broken-in-jdk7-gcm/
 */
public class AesGcmWithBcInputStreamTest {
    private static final SecureRandom secureRandom = new SecureRandom();

    static {
        Logging.init();
        Security.addProvider(new BouncyCastleProvider());
    }

    @Test
    public void testD_BouncyCastleCipherInputStreamWithAesGcm()
            throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException {
        // Encrypt (not interesting in this example)
        byte[] randomKey = createRandomArray(16);
        byte[] randomIv = createRandomArray(16);
        byte[] originalPlaintext = "Confirm 100$ pay".getBytes("ASCII");
        byte[] originalCiphertext = encryptWithAesGcm(originalPlaintext, randomKey, randomIv);

        // Attack / alter ciphertext (an attacker would do this!) 
        byte[] alteredCiphertext = Arrays.clone(originalCiphertext);
        alteredCiphertext[8] = (byte) (alteredCiphertext[8] ^ 0x08); // <<< Change 100$ to 900$

        // Decrypt with BouncyCastle implementation of CipherInputStream
        AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
        cipher.init(false, new AEADParameters(new KeyParameter(randomKey), 128, randomIv));

        try {
            readFromStream(new org.bouncycastle.crypto.io.CipherInputStream(
                    new ByteArrayInputStream(alteredCiphertext), cipher));
            //             ^^^^^^^^^^^^^^^ INTERESTING PART ^^^^^^^^^^^^^^^^   
            //
            //  The BouncyCastle implementation of the CipherInputStream detects MAC verification errors and
            //  throws a InvalidCipherTextIOException if an error occurs. Nice! A more or less minor issue
            //  however is that it is incompatible with the standard JCE Cipher class from the javax.crypto 
            //  package. The new interface AEADBlockCipher must be used. The code below is not executed.      

            fail("Test D: org.bouncycastle.crypto.io.CipherInputStream:        NOT OK, tampering not detected");
        } catch (InvalidCipherTextIOException e) {
            System.out
                    .println("Test D: org.bouncycastle.crypto.io.CipherInputStream:        OK, tampering detected");
        }
    }

    @Test
    public void testE_BouncyCastleCipherInputStreamWithAesGcmLongPlaintext()
            throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException {
        // Encrypt (not interesting in this example)
        byte[] randomKey = createRandomArray(16);
        byte[] randomIv = createRandomArray(16);
        byte[] originalPlaintext = createRandomArray(4080); // <<<< 4080 bytes fails, 4079 bytes works!    
        byte[] originalCiphertext = encryptWithAesGcm(originalPlaintext, randomKey, randomIv);

        // Decrypt with BouncyCastle implementation of CipherInputStream
        AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
        cipher.init(false, new AEADParameters(new KeyParameter(randomKey), 128, randomIv));

        try {
            readFromStream(new org.bouncycastle.crypto.io.CipherInputStream(
                    new ByteArrayInputStream(originalCiphertext), cipher));
            //             ^^^^^^^^^^^^^^^ INTERESTING PART ^^^^^^^^^^^^^^^^   
            //
            //  In this example, the BouncyCastle implementation of the CipherInputStream throws an ArrayIndexOutOfBoundsException.
            //  The only difference to the example above is that the plaintext is now 4080 bytes long! For 4079 bytes plaintexts,
            //  everything works just fine.

            System.out.println(
                    "Test E: org.bouncycastle.crypto.io.CipherInputStream:        OK, throws no exception");
        } catch (IOException e) {
            fail("Test E: org.bouncycastle.crypto.io.CipherInputStream:        NOT OK throws: " + e.getMessage());
        }
    }

    private static byte[] readFromStream(InputStream inputStream) throws IOException {
        ByteArrayOutputStream decryptedPlaintextOutputStream = new ByteArrayOutputStream();

        int read = -1;
        byte[] buffer = new byte[16];

        while (-1 != (read = inputStream.read(buffer))) {
            decryptedPlaintextOutputStream.write(buffer, 0, read);
        }

        inputStream.close();
        decryptedPlaintextOutputStream.close();

        return decryptedPlaintextOutputStream.toByteArray();
    }

    private static byte[] encryptWithAesGcm(byte[] plaintext, byte[] randomKeyBytes, byte[] randomIvBytes)
            throws IOException, InvalidKeyException, InvalidAlgorithmParameterException, NoSuchAlgorithmException,
            NoSuchProviderException, NoSuchPaddingException {

        SecretKey randomKey = new SecretKeySpec(randomKeyBytes, "AES");

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding", "BC");
        cipher.init(Cipher.ENCRYPT_MODE, randomKey, new IvParameterSpec(randomIvBytes));

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        CipherOutputStream cipherOutputStream = new CipherOutputStream(byteArrayOutputStream, cipher);

        cipherOutputStream.write(plaintext);
        cipherOutputStream.close();

        return byteArrayOutputStream.toByteArray();
    }

    private static byte[] createRandomArray(int size) {
        byte[] randomByteArray = new byte[size];
        secureRandom.nextBytes(randomByteArray);

        return randomByteArray;
    }
}