http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/cipher/OpensslNative.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/cipher/OpensslNative.java b/src/main/java/org/apache/commons/crypto/cipher/OpensslNative.java new file mode 100644 index 0000000..57a8cc7 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/cipher/OpensslNative.java @@ -0,0 +1,116 @@ +/** + * 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.commons.crypto.cipher; + +import java.nio.ByteBuffer; + +/** + * JNI interface of {@link Openssl} implementation. The native method in this class is + * defined in OpensslNative.h(genereted by javah). + */ +public class OpensslNative { + + /** + * Declares a native method to initialize JNI field and method IDs. + */ + public native static void initIDs(); + + /** + * Declares a native method to initialize the cipher context. + * + * @param algorithm The algorithm name of cipher + * @param padding The padding name of cipher + * @return the context address of cipher + */ + public native static long initContext(int algorithm, int padding); + + /** + * Declares a native method to initialize the cipher context. + * + * @return the context address of cipher + */ + public native static long init(long context, int mode, int alg, int padding, + byte[] key, byte[] iv); + + /** + * Continues a multiple-part encryption/decryption operation. The data + * is encrypted or decrypted, depending on how this cipher was initialized. + * + * @param context The cipher context address + * @param input The input byte buffer + * @param inputOffset The offset in input where the input starts + * @param inputLength The input length + * @param output The byte buffer for the result + * @param outputOffset The offset in output where the result is stored + * @param maxOutputLength The maximum length for output + * @return The number of bytes stored in output + */ + public native static int update(long context, ByteBuffer input, + int inputOffset, int inputLength, ByteBuffer output, int outputOffset, + int maxOutputLength); + + /** + * Continues a multiple-part encryption/decryption operation. The data + * is encrypted or decrypted, depending on how this cipher was initialized. + * + * @param context The cipher context address + * @param input The input byte array + * @param inputOffset The offset in input where the input starts + * @param inputLength The input length + * @param output The byte array for the result + * @param outputOffset The offset in output where the result is stored + * @param maxOutputLength The maximum length for output + * @return The number of bytes stored in output + */ + public native static int updateByteArray(long context, byte[] input, + int inputOffset, int inputLength, byte[] output, int outputOffset, + int maxOutputLength); + + /** + * Finishes a multiple-part operation. The data is encrypted or decrypted, + * depending on how this cipher was initialized. + * + * @param context The cipher context address + * @param output The byte buffer for the result + * @param offset The offset in output where the result is stored + * @param maxOutputLength The maximum length for output + * @return The number of bytes stored in output + */ + public native static int doFinal(long context, ByteBuffer output, int offset, + int maxOutputLength); + + /** + * Finishes a multiple-part operation. The data is encrypted or decrypted, + * depending on how this cipher was initialized. + * + * @param context The cipher context address + * @param output The byte array for the result + * @param offset The offset in output where the result is stored + * @param maxOutputLength The maximum length for output + * @return The number of bytes stored in output + */ + public native static int doFinalByteArray(long context, byte[] output, int offset, + int maxOutputLength); + + /** + * Cleans the context at native. + * + * @param context The cipher context address + */ + public native static void clean(long context); +}
http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/cipher/package-info.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/cipher/package-info.java b/src/main/java/org/apache/commons/crypto/cipher/package-info.java new file mode 100644 index 0000000..c650b57 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/cipher/package-info.java @@ -0,0 +1,22 @@ +/** + * 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. + */ + +/** + * Cipher classes + */ +package org.apache.commons.crypto.cipher; http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/conf/ConfigurationKeys.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/conf/ConfigurationKeys.java b/src/main/java/org/apache/commons/crypto/conf/ConfigurationKeys.java new file mode 100644 index 0000000..d9c193f --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/conf/ConfigurationKeys.java @@ -0,0 +1,139 @@ +/** + * 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.commons.crypto.conf; + +import org.apache.commons.crypto.cipher.JceCipher; + +/** + * The ConfigurationKeys contains Configuration keys and default values. + */ +public class ConfigurationKeys { + + /** + * The prefix named with project name. + */ + public static final String CHIMERA_PREFIX = "chimera."; + + /** + * The filename of configuration file. + */ + public static final String CHIMERA_SYSTEM_PROPERTIES_FILE = + CHIMERA_PREFIX + "properties"; + + /** + * The prefix of crypto configuration. + */ + public static final String CONF_PREFIX = + CHIMERA_PREFIX + "crypto."; + + /** + * The configuration key of implementation class for crypto cipher. + * The values of CHIMERA_CRYPTO_CIPHER_CLASSES_KEY can be + * "org.apache.commons.crypto.cipher.JceCipher" and "org.apache.commons.crypto.cipher.OpensslCipher". + * And it takes a common separated list. + * The "org.apache.commons.crypto.cipher.JceCipher" use jce provider to + * implement {@link org.apache.commons.crypto.cipher.Cipher} and + * the "org.apache.commons.crypto.cipher.OpensslCipher" use jni into openssl to implement. + * Note that for each value,the first value which can be created without exception + * will be used (priority by order). + */ + public static final String CHIMERA_CRYPTO_CIPHER_CLASSES_KEY = + CONF_PREFIX + "cipher.classes"; + + /** + * The default value for crypto cipher. + */ + public static final String CHIMERA_CRYPTO_CIPHER_CLASSES_DEFAULT = + JceCipher.class.getName(); + + /** + * The configuration key of the provider class for JCE cipher. + */ + public static final String CHIMERA_CRYPTO_CIPHER_JCE_PROVIDER_KEY = + CONF_PREFIX + "cipher.jce.provider"; + + // security random related configuration keys + /** + * The configuration key of the file path for secure random device. + */ + public static final String CHIMERA_CRYPTO_SECURE_RANDOM_DEVICE_FILE_PATH_KEY = + CONF_PREFIX + "secure.random.device.file.path"; + + /** + * The default value of the file path for secure random device. + */ + public static final String CHIMERA_CRYPTO_SECURE_RANDOM_DEVICE_FILE_PATH_DEFAULT = + "/dev/urandom"; + + /** + * The configuration key of the algorithm of secure random. + */ + public static final String CHIMERA_CRYPTO_SECURE_RANDOM_JAVA_ALGORITHM_KEY = + CONF_PREFIX + "secure.random.java.algorithm"; + + /** + * The default value of the algorithm of secure random. + */ + public static final String + CHIMERA_CRYPTO_SECURE_RANDOM_JAVA_ALGORITHM_DEFAULT = "SHA1PRNG"; + + /** + * The configuration key of the implementation class for secure random. + * The values of CHIMERA_CRYPTO_SECURE_RANDOM_CLASSES_KEY can be + * "org.apache.commons.crypto.random.JavaSecureRandom" and "org.apache.commons.crypto.random.OpensslSecureRandom". + * And it takes a common separated list. + * The "org.apache.commons.crypto.random.JavaSecureRandom" use java to + * implement {@link org.apache.commons.crypto.random.SecureRandom} and + * the "org.apache.commons.crypto.random.OpensslSecureRandom" use jni into openssl to implement. + * Note that for each value,the first value which can be created without exception + * will be used (priority by order). + */ + public static final String CHIMERA_CRYPTO_SECURE_RANDOM_CLASSES_KEY = + CONF_PREFIX + "secure.random.classes"; + + /** + * The configuration key of the buffer size for stream. + */ + public static final String CHIMERA_CRYPTO_STREAM_BUFFER_SIZE_KEY = + CONF_PREFIX + "stream.buffer.size"; + + // stream related configuration keys + /** + * The default value of the buffer size for stream. + */ + public static final int CHIMERA_CRYPTO_STREAM_BUFFER_SIZE_DEFAULT = 8192; + + // native lib related configuration keys + /** + * The configuration key of the path for loading crypto library. + */ + public static final String CHIMERA_CRYPTO_LIB_PATH_KEY = + CONF_PREFIX + "lib.path"; + + /** + * The configuration key of the file name for loading crypto library. + */ + public static final String CHIMERA_CRYPTO_LIB_NAME_KEY = + CONF_PREFIX + "lib.name"; + + /** + * The configuration key of temp directory for extracting crypto library. + */ + public static final String CHIMERA_CRYPTO_LIB_TEMPDIR_KEY = + CONF_PREFIX + "lib.tempdir"; +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/conf/package-info.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/conf/package-info.java b/src/main/java/org/apache/commons/crypto/conf/package-info.java new file mode 100644 index 0000000..837b195 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/conf/package-info.java @@ -0,0 +1,21 @@ +/** + * 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. + */ +/** + * Configuration classes + */ +package org.apache.commons.crypto.conf; http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/random/JavaSecureRandom.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/random/JavaSecureRandom.java b/src/main/java/org/apache/commons/crypto/random/JavaSecureRandom.java new file mode 100644 index 0000000..6c079d2 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/random/JavaSecureRandom.java @@ -0,0 +1,73 @@ +/** + * 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.commons.crypto.random; + +import java.security.NoSuchAlgorithmException; +import java.util.Properties; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.commons.crypto.conf.ConfigurationKeys; + +/** + * A SecureRandom of Java implementation. + */ +public class JavaSecureRandom implements SecureRandom { + private static final Log LOG = + LogFactory.getLog(JavaSecureRandom.class.getName()); + + private java.security.SecureRandom instance; + + /** + * Constructs a {@link org.apache.commons.crypto.random.JavaSecureRandom}. + * + * @param properties the configuration properties. + */ + public JavaSecureRandom(Properties properties) { + try { + instance = java.security.SecureRandom + .getInstance(properties.getProperty( + ConfigurationKeys.CHIMERA_CRYPTO_SECURE_RANDOM_JAVA_ALGORITHM_KEY, + ConfigurationKeys.CHIMERA_CRYPTO_SECURE_RANDOM_JAVA_ALGORITHM_DEFAULT)); + } catch (NoSuchAlgorithmException e) { + LOG.error("Failed to create java secure random due to error: " + e); + } + } + + /** + * Overrides {@link java.lang.AutoCloseable#close()}. + * For{@link JavaSecureRandom}, we don't need to recycle resource. + */ + @Override + public void close() { + // do nothing + } + + /** + * Overrides {@link org.apache.commons.crypto.random.SecureRandom#nextBytes(byte[])}. + * Generates random bytes and places them into a user-supplied byte array. + * The number of random bytes produced is equal to the length of the byte array. + * + * @param bytes the array to be filled in with random bytes. + */ + @Override + public void nextBytes(byte[] bytes) { + instance.nextBytes(bytes); + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandom.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandom.java b/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandom.java new file mode 100644 index 0000000..3794fde --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandom.java @@ -0,0 +1,140 @@ +/** + * 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.commons.crypto.random; + +import java.util.Properties; +import java.util.Random; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.apache.commons.crypto.utils.NativeCodeLoader; +import org.apache.commons.crypto.utils.Utils; + +/** + * OpenSSL secure random using JNI. + * This implementation is thread-safe. + * <p/> + * + * If using an Intel chipset with RDRAND, the high-performance hardware + * random number generator will be used and it's much faster than + * {@link java.security.SecureRandom}. If RDRAND is unavailable, default + * OpenSSL secure random generator will be used. It's still faster + * and can generate strong random bytes. + * <p/> + * @see https://wiki.openssl.org/index.php/Random_Numbers + * @see http://en.wikipedia.org/wiki/RdRand + */ +public class OpensslSecureRandom extends Random implements SecureRandom { + private static final long serialVersionUID = -7828193502768789584L; + private static final Log LOG = + LogFactory.getLog(OpensslSecureRandom.class.getName()); + + /** If native SecureRandom unavailable, use java SecureRandom */ + private JavaSecureRandom fallback = null; + private static boolean nativeEnabled = false; + static { + if (NativeCodeLoader.isNativeCodeLoaded()) { + try { + OpensslSecureRandomNative.initSR(); + nativeEnabled = true; + } catch (Throwable t) { + LOG.error("Failed to load Openssl SecureRandom", t); + } + } + } + + /** + * Judges whether loading native library successfully. + * + * @return true if loading library successfully. + */ + public static boolean isNativeCodeLoaded() { + return nativeEnabled; + } + + /** + * Constructs a {@link org.apache.commons.crypto.random.OpensslSecureRandom}. + * + * @param props the configuration properties. + */ + public OpensslSecureRandom(Properties props) { + if (!nativeEnabled) { + fallback = new JavaSecureRandom(props); + } + } + + /** + * Generates a user-specified number of random bytes. + * It's thread-safe. + * + * @param bytes the array to be filled in with random bytes. + */ + @Override + public void nextBytes(byte[] bytes) { + if (!nativeEnabled || !OpensslSecureRandomNative.nextRandBytes(bytes)) { + fallback.nextBytes(bytes); + } + } + + /** + * Overrides {@link OpensslSecureRandom}. + * For {@link OpensslSecureRandom}, we don't need to set seed. + * + * @param seed the initial seed. + */ + @Override + public void setSeed(long seed) { + // Self-seeding. + } + + /** + * Overrides {@link java.util.Random# next()}. Generates an integer + * containing the user-specified number of random + * bits(right justified, with leading zeros). + * + * @param numBits number of random bits to be generated, where + * 0 <= <code>numBits</code> <= 32. + * @return int an <code>int</code> containing the user-specified number + * of random bits (right justified, with leading zeros). + */ + @Override + final protected int next(int numBits) { + Utils.checkArgument(numBits >= 0 && numBits <= 32); + int numBytes = (numBits + 7) / 8; + byte b[] = new byte[numBytes]; + int next = 0; + + nextBytes(b); + for (int i = 0; i < numBytes; i++) { + next = (next << 8) + (b[i] & 0xFF); + } + + return next >>> (numBytes * 8 - numBits); + } + + /** + * Overrides {@link java.lang.AutoCloseable#close()}. Closes openssl context if native enabled. + */ + @Override + public void close() { + if (!nativeEnabled && fallback !=null) { + fallback.close(); + } + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandomNative.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandomNative.java b/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandomNative.java new file mode 100644 index 0000000..d80cf89 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/random/OpensslSecureRandomNative.java @@ -0,0 +1,40 @@ +/** + * 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.commons.crypto.random; + +/** + * JNI interface of {@link SecureRandom} implementation. + * The native method in this class is defined in + * OpensslSecureRandomNative.h(genereted by javah). + */ +public class OpensslSecureRandomNative { + /** + * Declares a native method to initialize SR. + */ + public native static void initSR(); + + /** + * Judges whether use {@link OpensslSecureRandomNative} to + * generate the user-specified number of random bits. + * + * @param bytes the array to be filled in with random bytes. + * @return true if use {@link OpensslSecureRandomNative} to + * generate the user-specified number of random bits. + */ + public native static boolean nextRandBytes(byte[] bytes); +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/random/OsSecureRandom.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/random/OsSecureRandom.java b/src/main/java/org/apache/commons/crypto/random/OsSecureRandom.java new file mode 100644 index 0000000..916ad10 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/random/OsSecureRandom.java @@ -0,0 +1,133 @@ +/** + * 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.commons.crypto.random; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Properties; +import java.util.Random; + +import org.apache.commons.crypto.utils.IOUtils; +import org.apache.commons.crypto.utils.Utils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A Random implementation that uses random bytes sourced from the + * operating system. + */ +public class OsSecureRandom extends Random implements SecureRandom { + public static final Log LOG = LogFactory.getLog(OsSecureRandom.class); + + private static final long serialVersionUID = 6391500337172057900L; + + private final int RESERVOIR_LENGTH = 8192; + + private String randomDevPath; + + private transient FileInputStream stream; + + private final byte[] reservoir = new byte[RESERVOIR_LENGTH]; + + private int pos = reservoir.length; + + private void fillReservoir(int min) { + if (pos >= reservoir.length - min) { + try { + IOUtils.readFully(stream, reservoir, 0, reservoir.length); + } catch (IOException e) { + throw new RuntimeException("failed to fill reservoir", e); + } + pos = 0; + } + } + + /** + * Constructs a {@link org.apache.commons.crypto.random.OsSecureRandom}. + * + * @param props the configuration properties. + */ + public OsSecureRandom(Properties props) { + randomDevPath = Utils.getRandomDevPath(props); + File randomDevFile = new File(randomDevPath); + + try { + close(); + this.stream = new FileInputStream(randomDevFile); + } catch (IOException e) { + throw new RuntimeException(e); + } + + try { + fillReservoir(0); + } catch (RuntimeException e) { + close(); + throw e; + } + } + + /** + * Overrides {@link org.apache.commons.crypto.random.SecureRandom#nextBytes(byte[])}. + * Generates random bytes and places them into a user-supplied byte array. + * The number of random bytes produced is equal to the length of the byte array. + * + * @param bytes the array to be filled in with random bytes. + */ + @Override + synchronized public void nextBytes(byte[] bytes) { + int off = 0; + int n = 0; + while (off < bytes.length) { + fillReservoir(0); + n = Math.min(bytes.length - off, reservoir.length - pos); + System.arraycopy(reservoir, pos, bytes, off, n); + off += n; + pos += n; + } + } + + /** + * Overrides {@link java.util.Random# next()}. Generates the next pseudorandom number. + * Subclasses should override this, as this is used by all other methods. + * + * @param nbits random bits. + * @return the next pseudorandom value from this random number + * generator's sequence. + */ + @Override + synchronized protected int next(int nbits) { + fillReservoir(4); + int n = 0; + for (int i = 0; i < 4; i++) { + n = ((n << 8) | (reservoir[pos++] & 0xff)); + } + return n & (0xffffffff >> (32 - nbits)); + } + + /** + * Overrides {@link java.lang.AutoCloseable#close()}. Closes the OS stream. + */ + @Override + synchronized public void close() { + if (stream != null) { + IOUtils.cleanup(LOG, stream); + stream = null; + } + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/random/SecureRandom.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/random/SecureRandom.java b/src/main/java/org/apache/commons/crypto/random/SecureRandom.java new file mode 100644 index 0000000..69c5559 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/random/SecureRandom.java @@ -0,0 +1,36 @@ +/** + * 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.commons.crypto.random; + +import java.io.Closeable; + +/** + * The interface for SecureRandom. + */ +public interface SecureRandom extends Closeable { + + /** + * Generates random bytes and places them into a user-supplied + * byte array. The number of random bytes produced is equal to + * the length of the byte array. + * + * @param bytes the byte array to fill with random bytes + */ + void nextBytes(byte[] bytes); + +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/random/SecureRandomFactory.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/random/SecureRandomFactory.java b/src/main/java/org/apache/commons/crypto/random/SecureRandomFactory.java new file mode 100644 index 0000000..104e5d8 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/random/SecureRandomFactory.java @@ -0,0 +1,72 @@ +/** + * 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.commons.crypto.random; + +import java.util.Properties; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.commons.crypto.utils.Utils; +import org.apache.commons.crypto.utils.ReflectionUtils; + +import static org.apache.commons.crypto.conf.ConfigurationKeys + .CHIMERA_CRYPTO_SECURE_RANDOM_CLASSES_KEY; + +/** + * This is the factory class used for {@link SecureRandom}. + */ +public class SecureRandomFactory { + public final static Logger LOG = LoggerFactory + .getLogger(SecureRandomFactory.class); + + /** + * Gets a SecureRandom instance for specified props. + * + * @param props the configuration properties. + * @return SecureRandom the secureRandom object.Null value will be returned if no + * SecureRandom classes with props. + */ + public static SecureRandom getSecureRandom(Properties props) { + String secureRandomClasses = props.getProperty( + CHIMERA_CRYPTO_SECURE_RANDOM_CLASSES_KEY); + if (secureRandomClasses == null) { + secureRandomClasses = System.getProperty( + CHIMERA_CRYPTO_SECURE_RANDOM_CLASSES_KEY); + } + + SecureRandom random = null; + if (secureRandomClasses != null) { + for (String klassName : Utils.splitClassNames(secureRandomClasses, ",")) { + try { + final Class<?> klass = ReflectionUtils.getClassByName(klassName); + random = (SecureRandom) ReflectionUtils.newInstance(klass, props); + if (random != null) { + break; + } + } catch (ClassCastException e) { + LOG.error("Class {} is not a Cipher.", klassName); + } catch (ClassNotFoundException e) { + LOG.error("Cipher {} not found.", klassName); + } + } + } + + return (random == null) ? new JavaSecureRandom(props) : random; + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/random/package-info.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/random/package-info.java b/src/main/java/org/apache/commons/crypto/random/package-info.java new file mode 100644 index 0000000..f8fd4de --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/random/package-info.java @@ -0,0 +1,21 @@ +/** + * 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. + */ +/** + * Random classes + */ +package org.apache.commons.crypto.random; http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java new file mode 100644 index 0000000..fc76f16 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CTRCryptoInputStream.java @@ -0,0 +1,423 @@ +/** + * 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.commons.crypto.stream; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +import org.apache.commons.crypto.cipher.Cipher; +import org.apache.commons.crypto.cipher.CipherTransformation; +import org.apache.commons.crypto.stream.input.ChannelInput; +import org.apache.commons.crypto.stream.input.Input; +import org.apache.commons.crypto.stream.input.StreamInput; +import org.apache.commons.crypto.utils.Utils; + +/** + * CTRCryptoInputStream decrypts data. AES CTR mode is required in order to + * ensure that the plain text and cipher text have a 1:1 mapping. CTR crypto + * stream has stream characteristic which is useful for implement features + * like random seek. The decryption is buffer based. The key points of the + * decryption are (1) calculating the counter and (2) padding through stream + * position: + * <p/> + * counter = base + pos/(algorithm blocksize); + * padding = pos%(algorithm blocksize); + * <p/> + * The underlying stream offset is maintained as state. It is not thread-safe. + */ +public class CTRCryptoInputStream extends CryptoInputStream { + /** + * Underlying stream offset + */ + protected long streamOffset = 0; + + /** + * Padding = pos%(algorithm blocksize); Padding is put into {@link #inBuffer} + * before any other data goes in. The purpose of padding is to put the input + * data at proper position. + */ + private byte padding; + + /** + * Flag to mark whether the cipher has been reset + */ + private boolean cipherReset = false; + + public CTRCryptoInputStream(Properties props, InputStream in, + byte[] key, byte[] iv) + throws IOException { + this(props, in, key, iv, 0); + } + + public CTRCryptoInputStream(Properties props, ReadableByteChannel in, + byte[] key, byte[] iv) + throws IOException { + this(props, in, key, iv, 0); + } + + public CTRCryptoInputStream(InputStream in, Cipher cipher, int bufferSize, + byte[] key, byte[] iv) throws IOException { + this(in, cipher, bufferSize, key, iv, 0); + } + + public CTRCryptoInputStream(ReadableByteChannel in, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(in, cipher, bufferSize, key, iv, 0); + } + + public CTRCryptoInputStream( + Input input, + Cipher cipher, + int bufferSize, + byte[] key, + byte[] iv) throws IOException { + this(input, cipher, bufferSize, key, iv, 0); + } + + public CTRCryptoInputStream(Properties props, InputStream in, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(in, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCryptoInputStream(Properties props, ReadableByteChannel in, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(in, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCryptoInputStream(InputStream in, Cipher cipher, int bufferSize, + byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new StreamInput(in, bufferSize), cipher, bufferSize, key, iv, streamOffset); + } + + public CTRCryptoInputStream(ReadableByteChannel in, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new ChannelInput(in), cipher, bufferSize, key, iv, streamOffset); + } + + public CTRCryptoInputStream( + Input input, + Cipher cipher, + int bufferSize, + byte[] key, + byte[] iv, + long streamOffset) throws IOException { + super(input, cipher, bufferSize, key, iv); + + Utils.checkStreamCipher(cipher); + + resetStreamOffset(streamOffset); + } + + /** Skip n bytes */ + @Override + public long skip(long n) throws IOException { + Utils.checkArgument(n >= 0, "Negative skip length."); + checkStream(); + + if (n == 0) { + return 0; + } else if (n <= outBuffer.remaining()) { + int pos = outBuffer.position() + (int) n; + outBuffer.position(pos); + return n; + } else { + /* + * Subtract outBuffer.remaining() to see how many bytes we need to + * skip in the underlying stream. Add outBuffer.remaining() to the + * actual number of skipped bytes in the underlying stream to get the + * number of skipped bytes from the user's point of view. + */ + n -= outBuffer.remaining(); + long skipped = input.skip(n); + if (skipped < 0) { + skipped = 0; + } + long pos = streamOffset + skipped; + skipped += outBuffer.remaining(); + resetStreamOffset(pos); + return skipped; + } + } + + /** ByteBuffer read. */ + @Override + public int read(ByteBuffer buf) throws IOException { + checkStream(); + int unread = outBuffer.remaining(); + if (unread <= 0) { // Fill the unread decrypted data buffer firstly + final int n = input.read(inBuffer); + if (n <= 0) { + return n; + } + + streamOffset += n; // Read n bytes + if (buf.isDirect() && buf.remaining() >= inBuffer.position() && padding == 0) { + // Use buf as the output buffer directly + decryptInPlace(buf); + padding = postDecryption(streamOffset); + return n; + } else { + // Use outBuffer as the output buffer + decrypt(); + padding = postDecryption(streamOffset); + } + } + + // Copy decrypted data from outBuffer to buf + unread = outBuffer.remaining(); + final int toRead = buf.remaining(); + if (toRead <= unread) { + final int limit = outBuffer.limit(); + outBuffer.limit(outBuffer.position() + toRead); + buf.put(outBuffer); + outBuffer.limit(limit); + return toRead; + } else { + buf.put(outBuffer); + return unread; + } + } + + /** + * Seek the stream to a specific position relative to start of the under layer stream. + * + * @param position The position to seek to + * @throws IOException if seek failed + */ + public void seek(long position) throws IOException { + Utils.checkArgument(position >= 0, "Cannot seek to negative offset."); + checkStream(); + /* + * If data of target pos in the underlying stream has already been read + * and decrypted in outBuffer, we just need to re-position outBuffer. + */ + if (position >= getStreamPosition() && position <= getStreamOffset()) { + int forward = (int) (position - getStreamPosition()); + if (forward > 0) { + outBuffer.position(outBuffer.position() + forward); + } + } else { + input.seek(position); + resetStreamOffset(position); + } + } + + protected long getStreamOffset() { + return streamOffset; + } + + protected long getStreamPosition() { + return streamOffset - outBuffer.remaining(); + } + + /** + * Decrypt more data by reading the under layer stream. The decrypted data will + * be put in the output buffer. + * + * @return The number of decrypted data. -1 if end of the decrypted stream + */ + protected int decryptMore() throws IOException { + int n = input.read(inBuffer); + if (n <= 0) { + return n; + } + + streamOffset += n; // Read n bytes + decrypt(); + padding = postDecryption(streamOffset); + return outBuffer.remaining(); + } + + /** + * Do the decryption using inBuffer as input and outBuffer as output. + * Upon return, inBuffer is cleared; the decrypted data starts at + * outBuffer.position() and ends at outBuffer.limit(); + */ + protected void decrypt() throws IOException { + Utils.checkState(inBuffer.position() >= padding); + if(inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + + inBuffer.flip(); + outBuffer.clear(); + decryptBuffer(outBuffer); + inBuffer.clear(); + outBuffer.flip(); + + if (padding > 0) { + /* + * The plain text and cipher text have a 1:1 mapping, they start at the + * same position. + */ + outBuffer.position(padding); + } + } + + /** + * Do the decryption using inBuffer as input and buf as output. + * Upon return, inBuffer is cleared; the buf's position will be equal to + * <i>p</i> <tt>+</tt> <i>n</i> where <i>p</i> is the position before + * decryption, <i>n</i> is the number of bytes decrypted. + * The buf's limit will not have changed. + */ + protected void decryptInPlace(ByteBuffer buf) throws IOException { + Utils.checkState(inBuffer.position() >= padding); + Utils.checkState(buf.isDirect()); + Utils.checkState(buf.remaining() >= inBuffer.position()); + Utils.checkState(padding == 0); + + if(inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + inBuffer.flip(); + decryptBuffer(buf); + inBuffer.clear(); + } + + /** + * Decrypt all data in buf: total n bytes from given start position. + * Output is also buf and same start position. + * buf.position() and buf.limit() should be unchanged after decryption. + */ + protected void decrypt(ByteBuffer buf, int offset, int len) + throws IOException { + final int pos = buf.position(); + final int limit = buf.limit(); + int n = 0; + while (n < len) { + buf.position(offset + n); + buf.limit(offset + n + Math.min(len - n, inBuffer.remaining())); + inBuffer.put(buf); + // Do decryption + try { + decrypt(); + buf.position(offset + n); + buf.limit(limit); + n += outBuffer.remaining(); + buf.put(outBuffer); + } finally { + padding = postDecryption(streamOffset - (len - n)); + } + } + buf.position(pos); + } + + /** + * This method is executed immediately after decryption. Check whether + * cipher should be updated and recalculate padding if needed. + */ + protected byte postDecryption(long position) throws IOException { + byte padding = 0; + if (cipherReset) { + /* + * This code is generally not executed since the cipher usually + * maintains cipher context (e.g. the counter) internally. However, + * some implementations can't maintain context so a re-init is necessary + * after each decryption call. + */ + resetCipher(position); + padding = getPadding(position); + inBuffer.position(padding); + } + return padding; + } + + protected long getCounter(long position) { + return position / cipher.getTransformation().getAlgorithmBlockSize(); + } + + protected byte getPadding(long position) { + return (byte)(position % cipher.getTransformation().getAlgorithmBlockSize()); + } + + /** Initialize the cipher. */ + @Override + protected void initCipher() { + // Do nothing for initCipher + // Will reset the cipher when reset the stream offset + } + + /** Calculate the counter and iv, reset the cipher. */ + protected void resetCipher(long position) + throws IOException { + final long counter = getCounter(position); + Utils.calculateIV(initIV, counter, iv); + try { + cipher.init(Cipher.DECRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + cipherReset = false; + } + + /** + * Reset the underlying stream offset; clear {@link #inBuffer} and + * {@link #outBuffer}. This Typically happens during {@link #skip(long)}. + */ + protected void resetStreamOffset(long offset) throws IOException { + streamOffset = offset; + inBuffer.clear(); + outBuffer.clear(); + outBuffer.limit(0); + resetCipher(offset); + padding = getPadding(offset); + inBuffer.position(padding); // Set proper position for input data. + } + + protected void decryptBuffer(ByteBuffer out) + throws IOException { + int inputSize = inBuffer.remaining(); + try { + int n = cipher.update(inBuffer, out); + if (n < inputSize) { + /** + * Typically code will not get here. Cipher#update will consume all + * input data and put result in outBuffer. + * Cipher#doFinal will reset the cipher context. + */ + cipher.doFinal(inBuffer, out); + cipherReset = true; + } + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } catch (BadPaddingException e) { + throw new IOException(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java new file mode 100644 index 0000000..cb282e3 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CTRCryptoOutputStream.java @@ -0,0 +1,230 @@ +/** + * 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.commons.crypto.stream; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +import org.apache.commons.crypto.cipher.Cipher; +import org.apache.commons.crypto.cipher.CipherTransformation; +import org.apache.commons.crypto.stream.output.ChannelOutput; +import org.apache.commons.crypto.stream.output.Output; +import org.apache.commons.crypto.stream.output.StreamOutput; +import org.apache.commons.crypto.utils.Utils; + +/** + * CTRCryptoOutputStream encrypts data. It is not thread-safe. AES CTR mode is + * required in order to ensure that the plain text and cipher text have a 1:1 + * mapping. The encryption is buffer based. The key points of the encryption are + * (1) calculating counter and (2) padding through stream position. + * <p/> + * counter = base + pos/(algorithm blocksize); + * padding = pos%(algorithm blocksize); + * <p/> + * The underlying stream offset is maintained as state. + */ +public class CTRCryptoOutputStream extends CryptoOutputStream { + /** + * Underlying stream offset. + */ + protected long streamOffset = 0; + + /** + * Padding = pos%(algorithm blocksize); Padding is put into {@link #inBuffer} + * before any other data goes in. The purpose of padding is to put input data + * at proper position. + */ + private byte padding; + + /** + * Flag to mark whether the cipher has been reset + */ + private boolean cipherReset = false; + + public CTRCryptoOutputStream(Properties props, OutputStream out, + byte[] key, byte[] iv) + throws IOException { + this(props, out, key, iv, 0); + } + + public CTRCryptoOutputStream(Properties props, WritableByteChannel out, + byte[] key, byte[] iv) + throws IOException { + this(props, out, key, iv, 0); + } + + public CTRCryptoOutputStream(OutputStream out, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(out, cipher, bufferSize, key, iv, 0); + } + + public CTRCryptoOutputStream(WritableByteChannel channel, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(channel, cipher, bufferSize, key, iv, 0); + } + + public CTRCryptoOutputStream(Output output, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) + throws IOException { + this(output, cipher, bufferSize, key, iv, 0); + } + + public CTRCryptoOutputStream(Properties props, OutputStream out, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(out, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCryptoOutputStream(Properties props, WritableByteChannel out, + byte[] key, byte[] iv, long streamOffset) + throws IOException { + this(out, Utils.getCipherInstance(CipherTransformation.AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public CTRCryptoOutputStream(OutputStream out, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new StreamOutput(out, bufferSize), cipher, + bufferSize, key, iv, streamOffset); + } + + public CTRCryptoOutputStream(WritableByteChannel channel, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) throws IOException { + this(new ChannelOutput(channel), cipher, + bufferSize, key, iv, streamOffset); + } + + public CTRCryptoOutputStream(Output output, Cipher cipher, + int bufferSize, byte[] key, byte[] iv, long streamOffset) + throws IOException { + super(output, cipher, bufferSize, key, iv); + + Utils.checkStreamCipher(cipher); + this.streamOffset = streamOffset; + + resetCipher(); + } + + /** + * Do the encryption, input is {@link #inBuffer} and output is + * {@link #outBuffer}. + */ + @Override + protected void encrypt() throws IOException { + Utils.checkState(inBuffer.position() >= padding); + if (inBuffer.position() == padding) { + // There is no real data in the inBuffer. + return; + } + + inBuffer.flip(); + outBuffer.clear(); + encryptBuffer(outBuffer); + inBuffer.clear(); + outBuffer.flip(); + + if (padding > 0) { + /* + * The plain text and cipher text have a 1:1 mapping, they start at the + * same position. + */ + outBuffer.position(padding); + padding = 0; + } + + final int len = output.write(outBuffer); + streamOffset += len; + if (cipherReset) { + /* + * This code is generally not executed since the encryptor usually + * maintains encryption context (e.g. the counter) internally. However, + * some implementations can't maintain context so a re-init is necessary + * after each encryption call. + */ + resetCipher(); + } + } + + /** + * Do final encryption of the last data + */ + @Override + protected void encryptFinal() throws IOException { + // The same as the normal encryption for Counter mode + encrypt(); + } + + /** Initialize the cipher. */ + @Override + protected void initCipher() { + // Do nothing for initCipher + // Will reset the cipher considering the stream offset + } + + /** Reset the {@link #cipher}: calculate counter and {@link #padding}. */ + private void resetCipher() throws IOException { + final long counter = + streamOffset / cipher.getTransformation().getAlgorithmBlockSize(); + padding = + (byte)(streamOffset % cipher.getTransformation().getAlgorithmBlockSize()); + inBuffer.position(padding); // Set proper position for input data. + + Utils.calculateIV(initIV, counter, iv); + try { + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + }catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + cipherReset = false; + } + + private void encryptBuffer(ByteBuffer out) + throws IOException { + int inputSize = inBuffer.remaining(); + try { + int n = cipher.update(inBuffer, out); + if (n < inputSize) { + /** + * Typically code will not get here. Cipher#update will consume all + * input data and put result in outBuffer. + * Cipher#doFinal will reset the cipher context. + */ + cipher.doFinal(inBuffer, out); + cipherReset = true; + } + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (BadPaddingException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java new file mode 100644 index 0000000..6498273 --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CryptoInputStream.java @@ -0,0 +1,394 @@ +/** + * 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.commons.crypto.stream; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.ReadableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +import org.apache.commons.crypto.cipher.Cipher; +import org.apache.commons.crypto.cipher.CipherTransformation; +import org.apache.commons.crypto.stream.input.ChannelInput; +import org.apache.commons.crypto.stream.input.Input; +import org.apache.commons.crypto.stream.input.StreamInput; +import org.apache.commons.crypto.utils.Utils; + +/** + * CryptoInputStream reads input data and decrypts data in stream manner. It supports + * any mode of operations such as AES CBC/CTR/GCM mode in concept.It is not thread-safe. + * + */ + +public class CryptoInputStream extends InputStream implements + ReadableByteChannel { + private final byte[] oneByteBuf = new byte[1]; + + protected final Cipher cipher; + protected final int bufferSize; + + protected final byte[] key; + protected final byte[] initIV; + protected byte[] iv; + + protected boolean closed; + protected boolean finalDone = false; + + protected Input input; + + /** + * Input data buffer. The data starts at inBuffer.position() and ends at + * to inBuffer.limit(). + */ + protected ByteBuffer inBuffer; + + /** + * The decrypted data buffer. The data starts at outBuffer.position() and + * ends at outBuffer.limit(); + */ + protected ByteBuffer outBuffer; + + public CryptoInputStream(CipherTransformation transformation, + Properties props, InputStream in, byte[] key, byte[] iv) + throws IOException { + this(in, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CryptoInputStream(CipherTransformation transformation, + Properties props, ReadableByteChannel in, byte[] key, byte[] iv) + throws IOException { + this(in, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CryptoInputStream(InputStream in, Cipher cipher, int bufferSize, + byte[] key, byte[] iv) throws IOException { + this(new StreamInput(in, bufferSize), cipher, bufferSize, key, iv); + } + + public CryptoInputStream(ReadableByteChannel in, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(new ChannelInput(in), cipher, bufferSize, key, iv); + } + + public CryptoInputStream( + Input input, + Cipher cipher, + int bufferSize, + byte[] key, + byte[] iv) throws IOException { + this.input = input; + this.cipher = cipher; + this.bufferSize = Utils.checkBufferSize(cipher, bufferSize); + this.key = key.clone(); + this.initIV = iv.clone(); + this.iv = iv.clone(); + + inBuffer = ByteBuffer.allocateDirect(this.bufferSize); + outBuffer = ByteBuffer.allocateDirect(this.bufferSize + + cipher.getTransformation().getAlgorithmBlockSize()); + outBuffer.limit(0); + + initCipher(); + } + + @Override + public int read() throws IOException { + int n; + while ((n = read(oneByteBuf, 0, 1)) == 0) ; + return (n == -1) ? -1 : oneByteBuf[0] & 0xff; + } + + /** + * Decryption is buffer based. + * If there is data in {@link #outBuffer}, then read it out of this buffer. + * If there is no data in {@link #outBuffer}, then read more from the + * underlying stream and do the decryption. + * @param b the buffer into which the decrypted data is read. + * @param off the buffer offset. + * @param len the maximum number of decrypted data bytes to read. + * @return int the total number of decrypted data bytes read into the buffer. + * @throws IOException + */ + @Override + public int read(byte[] b, int off, int len) throws IOException { + checkStream(); + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || len > b.length - off) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return 0; + } + + int remaining = outBuffer.remaining(); + if (remaining > 0) { + // Satisfy the read with the existing data + int n = Math.min(len, remaining); + outBuffer.get(b, off, n); + return n; + } else { + // No data in the out buffer, try read new data and decrypt it + int nd = decryptMore(); + if(nd <= 0) + return nd; + + int n = Math.min(len, outBuffer.remaining()); + outBuffer.get(b, off, n); + return n; + } + } + + @Override + public long skip(long n) throws IOException { + Utils.checkArgument(n >= 0, "Negative skip length."); + checkStream(); + + if (n == 0) { + return 0; + } + + long remaining = n; + int nd; + + while (remaining > 0) { + if(remaining <= outBuffer.remaining()) { + // Skip in the remaining buffer + int pos = outBuffer.position() + (int) remaining; + outBuffer.position(pos); + + remaining = 0; + break; + } else { + remaining -= outBuffer.remaining(); + outBuffer.clear(); + } + + nd = decryptMore(); + if (nd < 0) { + break; + } + } + + return n - remaining; + } + + @Override + public int available() throws IOException { + checkStream(); + + return input.available() + outBuffer.remaining(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + input.close(); + freeBuffers(); + cipher.close(); + super.close(); + closed = true; + } + + @Override + public void mark(int readlimit) { + } + + @Override + public void reset() throws IOException { + throw new IOException("Mark/reset not supported"); + } + + @Override + public boolean markSupported() { + return false; + } + + @Override + public boolean isOpen() { + return !closed; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + checkStream(); + int remaining = outBuffer.remaining(); + if (remaining <= 0) { + // Decrypt more data + int nd = decryptMore(); + if(nd < 0) { + return -1; + } + } + + // Copy decrypted data from outBuffer to dst + remaining = outBuffer.remaining(); + final int toRead = dst.remaining(); + if (toRead <= remaining) { + final int limit = outBuffer.limit(); + outBuffer.limit(outBuffer.position() + toRead); + dst.put(outBuffer); + outBuffer.limit(limit); + return toRead; + } else { + dst.put(outBuffer); + return remaining; + } + } + + /** + * Get the buffer size + */ + protected int getBufferSize() { + return bufferSize; + } + + /** + * Get the key + */ + protected byte[] getKey() { + return key; + } + + /** + * Get the initialization vector + */ + protected byte[] getInitIV() { + return initIV; + } + + /** + * Get the internal Cipher + */ + protected Cipher getCipher() { + return cipher; + } + + /** Initialize the cipher. */ + protected void initCipher() + throws IOException { + try { + cipher.init(Cipher.DECRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch(InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + } + + /** + * Decrypt more data by reading the under layer stream. The decrypted data will + * be put in the output buffer. If the end of the under stream reached, we will + * do final of the cipher to finish all the decrypting of data. + * + * @return The number of decrypted data. -1 if end of the decrypted stream + */ + protected int decryptMore() throws IOException { + if(finalDone) { + return -1; + } + + int n = input.read(inBuffer); + if (n < 0) { + // The stream is end, finalize the cipher stream + decryptFinal(); + + // Satisfy the read with the remaining + int remaining = outBuffer.remaining(); + if (remaining > 0) { + return remaining; + } + + // End of the stream + return -1; + } else if(n == 0) { + // No data is read, but the stream is not end yet + return 0; + } else { + decrypt(); + return outBuffer.remaining(); + } + } + + /** + * Do the decryption using inBuffer as input and outBuffer as output. + * Upon return, inBuffer is cleared; the decrypted data starts at + * outBuffer.position() and ends at outBuffer.limit(); + */ + protected void decrypt() throws IOException { + // Prepare the input buffer and clear the out buffer + inBuffer.flip(); + outBuffer.clear(); + + try { + cipher.update(inBuffer, outBuffer); + } catch (ShortBufferException e) { + throw new IOException(e); + } + + // Clear the input buffer and prepare out buffer + inBuffer.clear(); + outBuffer.flip(); + } + + /** + * Do final of the cipher to end the decrypting stream + */ + protected void decryptFinal() throws IOException { + // Prepare the input buffer and clear the out buffer + inBuffer.flip(); + outBuffer.clear(); + + try { + cipher.doFinal(inBuffer, outBuffer); + finalDone = true; + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } catch( BadPaddingException e) { + throw new IOException(e); + } + + // Clear the input buffer and prepare out buffer + inBuffer.clear(); + outBuffer.flip(); + } + + protected void checkStream() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + /** Forcibly free the direct buffers. */ + protected void freeBuffers() { + Utils.freeDirectBuffer(inBuffer); + Utils.freeDirectBuffer(outBuffer); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java new file mode 100644 index 0000000..3d3ac8d --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/CryptoOutputStream.java @@ -0,0 +1,283 @@ +/** + * 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.commons.crypto.stream; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.WritableByteChannel; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +import org.apache.commons.crypto.cipher.Cipher; +import org.apache.commons.crypto.cipher.CipherTransformation; +import org.apache.commons.crypto.stream.output.ChannelOutput; +import org.apache.commons.crypto.stream.output.Output; +import org.apache.commons.crypto.stream.output.StreamOutput; +import org.apache.commons.crypto.utils.Utils; + +/** + * CryptoOutputStream encrypts data and writes to the under layer output. It supports + * any mode of operations such as AES CBC/CTR/GCM mode in concept. It is not thread-safe. + */ + +public class CryptoOutputStream extends OutputStream implements + WritableByteChannel { + private final byte[] oneByteBuf = new byte[1]; + + protected Output output; + protected final Cipher cipher; + protected final int bufferSize; + + protected final byte[] key; + protected final byte[] initIV; + protected byte[] iv; + + protected boolean closed; + + /** + * Input data buffer. The data starts at inBuffer.position() and ends at + * inBuffer.limit(). + */ + protected ByteBuffer inBuffer; + + /** + * Encrypted data buffer. The data starts at outBuffer.position() and ends at + * outBuffer.limit(); + */ + protected ByteBuffer outBuffer; + + public CryptoOutputStream(CipherTransformation transformation, + Properties props, OutputStream out, byte[] key, byte[] iv) + throws IOException { + this(out, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CryptoOutputStream(CipherTransformation transformation, + Properties props, WritableByteChannel out, byte[] key, byte[] iv) + throws IOException { + this(out, Utils.getCipherInstance(transformation, props), + Utils.getBufferSize(props), key, iv); + } + + public CryptoOutputStream(OutputStream out, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(new StreamOutput(out, bufferSize), cipher, bufferSize, key, iv); + } + + public CryptoOutputStream(WritableByteChannel channel, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) throws IOException { + this(new ChannelOutput(channel), cipher, bufferSize, key, iv); + } + + protected CryptoOutputStream(Output output, Cipher cipher, + int bufferSize, byte[] key, byte[] iv) + throws IOException { + + this.output = output; + this.bufferSize = Utils.checkBufferSize(cipher, bufferSize); + this.cipher = cipher; + this.key = key.clone(); + this.initIV = iv.clone(); + this.iv = iv.clone(); + inBuffer = ByteBuffer.allocateDirect(this.bufferSize); + outBuffer = ByteBuffer.allocateDirect(this.bufferSize + + cipher.getTransformation().getAlgorithmBlockSize()); + + initCipher(); + } + + @Override + public void write(int b) throws IOException { + oneByteBuf[0] = (byte)(b & 0xff); + write(oneByteBuf, 0, oneByteBuf.length); + } + + /** + * Encryption is buffer based. + * If there is enough room in {@link #inBuffer}, then write to this buffer. + * If {@link #inBuffer} is full, then do encryption and write data to the + * underlying stream. + * @param b the data. + * @param off the start offset in the data. + * @param len the number of bytes to write. + * @throws IOException + */ + public void write(byte[] b, int off, int len) throws IOException { + checkStream(); + if (b == null) { + throw new NullPointerException(); + } else if (off < 0 || len < 0 || off > b.length || + len > b.length - off) { + throw new IndexOutOfBoundsException(); + } + + while (len > 0) { + final int remaining = inBuffer.remaining(); + if (len < remaining) { + inBuffer.put(b, off, len); + len = 0; + } else { + inBuffer.put(b, off, remaining); + off += remaining; + len -= remaining; + encrypt(); + } + } + } + + /** + * To flush, we need to encrypt the data in the buffer and write to the + * underlying stream, then do the flush. + */ + @Override + public void flush() throws IOException { + checkStream(); + encrypt(); + output.flush(); + super.flush(); + } + + @Override + public void close() throws IOException { + if (closed) { + return; + } + + try { + encryptFinal(); + output.close(); + freeBuffers(); + cipher.close(); + super.close(); + } finally { + closed = true; + } + } + + @Override + public boolean isOpen() { + return !closed; + } + + @Override + public int write(ByteBuffer src) throws IOException { + checkStream(); + final int len = src.remaining(); + int remaining = len; + while (remaining > 0) { + final int space = inBuffer.remaining(); + if (remaining < space) { + inBuffer.put(src); + remaining = 0; + } else { + // to void copy twice, we set the limit to copy directly + final int oldLimit = src.limit(); + final int newLimit = src.position() + space; + src.limit(newLimit); + + inBuffer.put(src); + + // restore the old limit + src.limit(oldLimit); + + remaining -= space; + encrypt(); + } + } + + return len; + } + + /** Initialize the cipher. */ + protected void initCipher() + throws IOException { + try { + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch(InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + } + + /** + * Do the encryption, input is {@link #inBuffer} and output is + * {@link #outBuffer}. + */ + protected void encrypt() throws IOException { + + inBuffer.flip(); + outBuffer.clear(); + + try { + cipher.update(inBuffer, outBuffer); + } catch (ShortBufferException e) { + throw new IOException(e); + } + + inBuffer.clear(); + outBuffer.flip(); + + // write to output + output.write(outBuffer); + } + + /** + * Do final encryption of the last data + */ + protected void encryptFinal() throws IOException { + inBuffer.flip(); + outBuffer.clear(); + + try { + cipher.doFinal(inBuffer, outBuffer); + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } catch( BadPaddingException e) { + throw new IOException(e); + } + + inBuffer.clear(); + outBuffer.flip(); + + // write to output + output.write(outBuffer); + } + + protected void checkStream() throws IOException { + if (closed) { + throw new IOException("Stream closed"); + } + } + + /** Forcibly free the direct buffers. */ + protected void freeBuffers() { + Utils.freeDirectBuffer(inBuffer); + Utils.freeDirectBuffer(outBuffer); + } +} http://git-wip-us.apache.org/repos/asf/commons-crypto/blob/4920d272/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java b/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java new file mode 100644 index 0000000..160f6ee --- /dev/null +++ b/src/main/java/org/apache/commons/crypto/stream/PositionedCryptoInputStream.java @@ -0,0 +1,311 @@ +/** + * 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.commons.crypto.stream; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.util.Properties; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; + +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.ShortBufferException; + +import org.apache.commons.crypto.cipher.Cipher; +import org.apache.commons.crypto.cipher.CipherFactory; +import org.apache.commons.crypto.stream.input.Input; +import org.apache.commons.crypto.utils.IOUtils; +import org.apache.commons.crypto.utils.Utils; + +import static org.apache.commons.crypto.cipher.CipherTransformation.AES_CTR_NOPADDING; + +/** + * PositionedCryptoInputStream provides the capability to decrypt the stream starting + * at random position as well as provides the foundation for positioned read for + * decrypting. This needs a stream cipher mode such as AES CTR mode. + */ +public class PositionedCryptoInputStream extends CTRCryptoInputStream { + + /** + * DirectBuffer pool + */ + private final Queue<ByteBuffer> bufferPool = new + ConcurrentLinkedQueue<ByteBuffer>(); + + /** + * Cipher pool + */ + private final Queue<CipherState> cipherPool = new + ConcurrentLinkedQueue<CipherState>(); + + public PositionedCryptoInputStream(Properties props, Input in, + byte[] key, byte[] iv, long streamOffset) throws IOException { + this(in, Utils.getCipherInstance(AES_CTR_NOPADDING, props), + Utils.getBufferSize(props), key, iv, streamOffset); + } + + public PositionedCryptoInputStream( + Input input, + Cipher cipher, + int bufferSize, + byte[] key, + byte[] iv, + long streamOffset) throws IOException { + super(input, cipher, bufferSize, key, iv, streamOffset); + } + + /** + * Read upto the specified number of bytes from a given position + * within a stream and return the number of bytes read. This does not + * change the current offset of the stream, and is thread-safe. + */ + public int read(long position, byte[] buffer, int offset, int length) + throws IOException { + checkStream(); + final int n = input.read(position, buffer, offset, length); + if (n > 0) { + // This operation does not change the current offset of the file + decrypt(position, buffer, offset, n); + } + return n; + } + + /** + * Read the specified number of bytes from a given position within a stream. + * This does not change the current offset of the stream and is thread-safe. + */ + public void readFully(long position, byte[] buffer, int offset, int length) + throws IOException { + checkStream(); + IOUtils.readFully(input, position, buffer, offset, length); + if (length > 0) { + // This operation does not change the current offset of the file + decrypt(position, buffer, offset, length); + } + } + + public void readFully(long position, byte[] buffer) throws IOException { + readFully(position, buffer, 0, buffer.length); + } + + /** + * Decrypt length bytes in buffer starting at offset. Output is also put + * into buffer starting at offset. It is thread-safe. + */ + protected void decrypt(long position, byte[] buffer, int offset, int length) + throws IOException { + ByteBuffer inBuffer = getBuffer(); + ByteBuffer outBuffer = getBuffer(); + CipherState state = null; + try { + state = getCipherState(); + byte[] iv = getInitIV().clone(); + resetCipher(state, position, iv); + byte padding = getPadding(position); + inBuffer.position(padding); // Set proper position for input data. + + int n = 0; + while (n < length) { + int toDecrypt = Math.min(length - n, inBuffer.remaining()); + inBuffer.put(buffer, offset + n, toDecrypt); + + // Do decryption + decrypt(state, inBuffer, outBuffer, padding); + + outBuffer.get(buffer, offset + n, toDecrypt); + n += toDecrypt; + padding = postDecryption(state, inBuffer, position + n, iv); + } + } finally { + returnBuffer(inBuffer); + returnBuffer(outBuffer); + returnCipherState(state); + } + } + + /** + * Do the decryption using inBuffer as input and outBuffer as output. + * Upon return, inBuffer is cleared; the decrypted data starts at + * outBuffer.position() and ends at outBuffer.limit(); + */ + private void decrypt(CipherState state, ByteBuffer inBuffer, + ByteBuffer outBuffer, byte padding) throws IOException { + Utils.checkState(inBuffer.position() >= padding); + if(inBuffer.position() == padding) { + // There is no real data in inBuffer. + return; + } + inBuffer.flip(); + outBuffer.clear(); + decryptBuffer(state, inBuffer, outBuffer); + inBuffer.clear(); + outBuffer.flip(); + if (padding > 0) { + /* + * The plain text and cipher text have a 1:1 mapping, they start at the + * same position. + */ + outBuffer.position(padding); + } + } + + private void decryptBuffer(CipherState state, ByteBuffer inBuffer, ByteBuffer outBuffer) + throws IOException { + int inputSize = inBuffer.remaining(); + try { + int n = state.getCipher().update(inBuffer, outBuffer); + if (n < inputSize) { + /** + * Typically code will not get here. Cipher#update will consume all + * input data and put result in outBuffer. + * Cipher#doFinal will reset the cipher context. + */ + state.getCipher().doFinal(inBuffer, outBuffer); + state.reset(true); + } + } catch (ShortBufferException e) { + throw new IOException(e); + } catch (IllegalBlockSizeException e) { + throw new IOException(e); + } catch (BadPaddingException e) { + throw new IOException(e); + } + } + + /** + * This method is executed immediately after decryption. Check whether + * cipher should be updated and recalculate padding if needed. + */ + private byte postDecryption(CipherState state, ByteBuffer inBuffer, + long position, byte[] iv) throws IOException { + byte padding = 0; + if (state.isReset()) { + /* + * This code is generally not executed since the cipher usually + * maintains cipher context (e.g. the counter) internally. However, + * some implementations can't maintain context so a re-init is necessary + * after each decryption call. + */ + resetCipher(state, position, iv); + padding = getPadding(position); + inBuffer.position(padding); + } + return padding; + } + + /** Calculate the counter and iv, reset the cipher. */ + private void resetCipher(CipherState state, long position, byte[] iv) + throws IOException { + final long counter = getCounter(position); + Utils.calculateIV(getInitIV(), counter, iv); + try { + state.getCipher().init(Cipher.DECRYPT_MODE, getKey(), iv); + } catch (InvalidKeyException e) { + throw new IOException(e); + } catch (InvalidAlgorithmParameterException e) { + throw new IOException(e); + } + state.reset(false); + } + + /** Get Cipher from pool */ + private CipherState getCipherState() throws IOException { + CipherState state = cipherPool.poll(); + if (state == null) { + Cipher cipher; + try { + cipher = CipherFactory.getInstance(getCipher().getTransformation(), + getCipher().getProperties()); + } catch (GeneralSecurityException e) { + throw new IOException(e); + } + state = new CipherState(cipher); + } + + return state; + } + + /** Return Cipher to pool */ + private void returnCipherState(CipherState state) { + if (state != null) { + cipherPool.add(state); + } + } + + /** Get direct buffer from pool */ + private ByteBuffer getBuffer() { + ByteBuffer buffer = bufferPool.poll(); + if (buffer == null) { + buffer = ByteBuffer.allocateDirect(getBufferSize()); + } + + return buffer; + } + + /** Return direct buffer to pool */ + private void returnBuffer(ByteBuffer buf) { + if (buf != null) { + buf.clear(); + bufferPool.add(buf); + } + } + + @Override + public void close() throws IOException { + if (!isOpen()) { + return; + } + + cleanBufferPool(); + super.close(); + } + + /** Clean direct buffer pool */ + private void cleanBufferPool() { + ByteBuffer buf; + while ((buf = bufferPool.poll()) != null) { + Utils.freeDirectBuffer(buf); + } + } + + private class CipherState { + private Cipher cipher; + private boolean reset; + + public CipherState(Cipher cipher) { + this.cipher = cipher; + this.reset = false; + } + + public Cipher getCipher() { + return cipher; + } + + public boolean isReset() { + return reset; + } + + public void reset(boolean reset) { + this.reset = reset; + } + } +}