/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.crypto; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.BufferedInputStream; import java.io.DataInputStream; import java.io.IOException; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.SecureRandom; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Random; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.io.DataInputBuffer; import org.apache.hadoop.io.DataOutputBuffer; import org.apache.hadoop.io.RandomDatum; import org.apache.hadoop.test.GenericTestUtils; import org.apache.hadoop.util.NativeCodeLoader; import org.apache.hadoop.util.ReflectionUtils; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Test; import com.google.common.primitives.Longs; public class TestCryptoCodec { private static final Log LOG= LogFactory.getLog(TestCryptoCodec.class); private static byte[] key = new byte[16]; private static byte[] iv = new byte[16]; private static final int bufferSize = 4096; private Configuration conf = new Configuration(); private int count = 10000; private int seed = new Random().nextInt(); private final String jceCodecClass = "org.apache.hadoop.crypto.JceAesCtrCryptoCodec"; private final String opensslCodecClass = "org.apache.hadoop.crypto.OpensslAesCtrCryptoCodec"; @Before public void setUp() throws IOException { Random random = new SecureRandom(); random.nextBytes(key); random.nextBytes(iv); } @Test(timeout=120000) public void testJceAesCtrCryptoCodec() throws Exception { GenericTestUtils.assumeInNativeProfile(); if (!NativeCodeLoader.buildSupportsOpenssl()) { LOG.warn("Skipping test since openSSL library not loaded"); Assume.assumeTrue(false); } Assert.assertEquals(null, OpensslCipher.getLoadingFailureReason()); cryptoCodecTest(conf, seed, 0, jceCodecClass, jceCodecClass, iv); cryptoCodecTest(conf, seed, count, jceCodecClass, jceCodecClass, iv); cryptoCodecTest(conf, seed, count, jceCodecClass, opensslCodecClass, iv); // Overflow test, IV: xx xx xx xx xx xx xx xx ff ff ff ff ff ff ff ff for(int i = 0; i < 8; i++) { iv[8 + i] = (byte) 0xff; } cryptoCodecTest(conf, seed, count, jceCodecClass, jceCodecClass, iv); cryptoCodecTest(conf, seed, count, jceCodecClass, opensslCodecClass, iv); } @Test(timeout=120000) public void testOpensslAesCtrCryptoCodec() throws Exception { GenericTestUtils.assumeInNativeProfile(); if (!NativeCodeLoader.buildSupportsOpenssl()) { LOG.warn("Skipping test since openSSL library not loaded"); Assume.assumeTrue(false); } Assert.assertEquals(null, OpensslCipher.getLoadingFailureReason()); cryptoCodecTest(conf, seed, 0, opensslCodecClass, opensslCodecClass, iv); cryptoCodecTest(conf, seed, count, opensslCodecClass, opensslCodecClass, iv); cryptoCodecTest(conf, seed, count, opensslCodecClass, jceCodecClass, iv); // Overflow test, IV: xx xx xx xx xx xx xx xx ff ff ff ff ff ff ff ff for(int i = 0; i < 8; i++) { iv[8 + i] = (byte) 0xff; } cryptoCodecTest(conf, seed, count, opensslCodecClass, opensslCodecClass, iv); cryptoCodecTest(conf, seed, count, opensslCodecClass, jceCodecClass, iv); } private void cryptoCodecTest(Configuration conf, int seed, int count, String encCodecClass, String decCodecClass, byte[] iv) throws IOException, GeneralSecurityException { CryptoCodec encCodec = null; try { encCodec = (CryptoCodec)ReflectionUtils.newInstance( conf.getClassByName(encCodecClass), conf); } catch (ClassNotFoundException cnfe) { throw new IOException("Illegal crypto codec!"); } LOG.info("Created a Codec object of type: " + encCodecClass); // Generate data DataOutputBuffer data = new DataOutputBuffer(); RandomDatum.Generator generator = new RandomDatum.Generator(seed); for(int i = 0; i < count; ++i) { generator.next(); RandomDatum key = generator.getKey(); RandomDatum value = generator.getValue(); key.write(data); value.write(data); } LOG.info("Generated " + count + " records"); // Encrypt data DataOutputBuffer encryptedDataBuffer = new DataOutputBuffer(); CryptoOutputStream out = new CryptoOutputStream(encryptedDataBuffer, encCodec, bufferSize, key, iv); out.write(data.getData(), 0, data.getLength()); out.flush(); out.close(); LOG.info("Finished encrypting data"); CryptoCodec decCodec = null; try { decCodec = (CryptoCodec)ReflectionUtils.newInstance( conf.getClassByName(decCodecClass), conf); } catch (ClassNotFoundException cnfe) { throw new IOException("Illegal crypto codec!"); } LOG.info("Created a Codec object of type: " + decCodecClass); // Decrypt data DataInputBuffer decryptedDataBuffer = new DataInputBuffer(); decryptedDataBuffer.reset(encryptedDataBuffer.getData(), 0, encryptedDataBuffer.getLength()); CryptoInputStream in = new CryptoInputStream(decryptedDataBuffer, decCodec, bufferSize, key, iv); DataInputStream dataIn = new DataInputStream(new BufferedInputStream(in)); // Check DataInputBuffer originalData = new DataInputBuffer(); originalData.reset(data.getData(), 0, data.getLength()); DataInputStream originalIn = new DataInputStream( new BufferedInputStream(originalData)); for(int i=0; i < count; ++i) { RandomDatum k1 = new RandomDatum(); RandomDatum v1 = new RandomDatum(); k1.readFields(originalIn); v1.readFields(originalIn); RandomDatum k2 = new RandomDatum(); RandomDatum v2 = new RandomDatum(); k2.readFields(dataIn); v2.readFields(dataIn); assertTrue("original and encrypted-then-decrypted-output not equal", k1.equals(k2) && v1.equals(v2)); // original and encrypted-then-decrypted-output have the same hashCode Map<RandomDatum, String> m = new HashMap<RandomDatum, String>(); m.put(k1, k1.toString()); m.put(v1, v1.toString()); String result = m.get(k2); assertEquals("k1 and k2 hashcode not equal", result, k1.toString()); result = m.get(v2); assertEquals("v1 and v2 hashcode not equal", result, v1.toString()); } // Decrypt data byte-at-a-time originalData.reset(data.getData(), 0, data.getLength()); decryptedDataBuffer.reset(encryptedDataBuffer.getData(), 0, encryptedDataBuffer.getLength()); in = new CryptoInputStream(decryptedDataBuffer, decCodec, bufferSize, key, iv); // Check originalIn = new DataInputStream(new BufferedInputStream(originalData)); int expected; do { expected = originalIn.read(); assertEquals("Decrypted stream read by byte does not match", expected, in.read()); } while (expected != -1); // Seek to a certain position and decrypt originalData.reset(data.getData(), 0, data.getLength()); decryptedDataBuffer.reset(encryptedDataBuffer.getData(), 0, encryptedDataBuffer.getLength()); in = new CryptoInputStream(new TestCryptoStreams.FakeInputStream( decryptedDataBuffer), decCodec, bufferSize, key, iv); int seekPos = data.getLength() / 3; in.seek(seekPos); // Check TestCryptoStreams.FakeInputStream originalInput = new TestCryptoStreams.FakeInputStream(originalData); originalInput.seek(seekPos); do { expected = originalInput.read(); assertEquals("Decrypted stream read by byte does not match", expected, in.read()); } while (expected != -1); LOG.info("SUCCESS! Completed checking " + count + " records"); // Check secure random generator testSecureRandom(encCodec); } /** Test secure random generator */ private void testSecureRandom(CryptoCodec codec) { // len = 16 checkSecureRandom(codec, 16); // len = 32 checkSecureRandom(codec, 32); // len = 128 checkSecureRandom(codec, 128); } private void checkSecureRandom(CryptoCodec codec, int len) { byte[] rand = new byte[len]; byte[] rand1 = new byte[len]; codec.generateSecureRandom(rand); codec.generateSecureRandom(rand1); Assert.assertEquals(len, rand.length); Assert.assertEquals(len, rand1.length); Assert.assertFalse(Arrays.equals(rand, rand1)); } /** * Regression test for IV calculation, see HADOOP-11343 */ @Test(timeout=120000) public void testCalculateIV() throws Exception { JceAesCtrCryptoCodec codec = new JceAesCtrCryptoCodec(); codec.setConf(conf); SecureRandom sr = new SecureRandom(); byte[] initIV = new byte[16]; byte[] IV = new byte[16]; long iterations = 1000; long counter = 10000; // Overflow test, IV: 00 00 00 00 00 00 00 00 ff ff ff ff ff ff ff ff for(int i = 0; i < 8; i++) { initIV[8 + i] = (byte)0xff; } for(long j = 0; j < counter; j++) { assertIVCalculation(codec, initIV, j, IV); } // Random IV and counter sequence test for(long i = 0; i < iterations; i++) { sr.nextBytes(initIV); for(long j = 0; j < counter; j++) { assertIVCalculation(codec, initIV, j, IV); } } // Random IV and random counter test for(long i = 0; i < iterations; i++) { sr.nextBytes(initIV); for(long j = 0; j < counter; j++) { long c = sr.nextLong(); assertIVCalculation(codec, initIV, c, IV); } } } private void assertIVCalculation(CryptoCodec codec, byte[] initIV, long counter, byte[] IV) { codec.calculateIV(initIV, counter, IV); BigInteger iv = new BigInteger(1, IV); BigInteger ref = calculateRef(initIV, counter); assertTrue("Calculated IV don't match with the reference", iv.equals(ref)); } private static BigInteger calculateRef(byte[] initIV, long counter) { byte[] cb = Longs.toByteArray(counter); BigInteger bi = new BigInteger(1, initIV); return bi.add(new BigInteger(1, cb)); } }