You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by br...@apache.org on 2023/11/13 13:41:03 UTC
(solr-sandbox) branch main updated: Encryption classes use arrays instead of ByteBuffers. (#86)
This is an automated email from the ASF dual-hosted git repository.
broustant pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-sandbox.git
The following commit(s) were added to refs/heads/main by this push:
new 9baea9c Encryption classes use arrays instead of ByteBuffers. (#86)
9baea9c is described below
commit 9baea9c15caabe8d1dc4fe128f7b9494ff03a29d
Author: Bruno Roustant <33...@users.noreply.github.com>
AuthorDate: Mon Nov 13 14:40:58 2023 +0100
Encryption classes use arrays instead of ByteBuffers. (#86)
---
.../solr/encryption/crypto/AesCtrEncrypter.java | 25 +++--
.../apache/solr/encryption/crypto/AesCtrUtil.java | 59 ++++++-----
.../encryption/crypto/CipherAesCtrEncrypter.java | 22 ++---
.../crypto/DecryptingChannelInputStream.java | 72 +++++++++-----
.../encryption/crypto/DecryptingIndexInput.java | 109 +++++++++++----------
.../encryption/crypto/DecryptingInputStream.java | 75 ++++++++------
.../encryption/crypto/EncryptingIndexOutput.java | 68 +++++++------
.../encryption/crypto/EncryptingOutputStream.java | 60 ++++++++----
.../encryption/crypto/LightAesCtrEncrypter.java | 18 +---
.../encryption/crypto/AesCtrEncrypterTest.java | 65 ++++++------
.../crypto/DecryptingIndexInputTest.java | 58 +++++------
.../crypto/EncryptingIndexOutputTest.java | 9 +-
12 files changed, 341 insertions(+), 299 deletions(-)
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrEncrypter.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrEncrypter.java
index 16fc33e..fd9ba1c 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrEncrypter.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrEncrypter.java
@@ -16,43 +16,42 @@
*/
package org.apache.solr.encryption.crypto;
-import java.nio.ByteBuffer;
-
/**
* Stateful Encrypter specialized for AES algorithm in CTR (counter) mode with no padding.
* <p>In CTR mode, encryption and decryption are actually the same operation, so this API does not require specifying
* whether it is used to encrypt or decrypt.
* <p>An {@link AesCtrEncrypter} must be first {@link #init(long) initialized} before it can be used to
- * {@link #process(ByteBuffer, ByteBuffer) encrypt/decrypt}.
+ * {@link #process encrypt/decrypt}.
* <p>Not thread safe.
*/
public interface AesCtrEncrypter extends Cloneable {
/**
- * Initializes this encrypter at the provided CTR block counter (counter of blocks of size {@link AesCtrUtil#AES_BLOCK_SIZE}).
+ * Initializes this encrypter at the provided CTR block counter (counter of blocks of size
+ * {@link AesCtrUtil#AES_BLOCK_SIZE}).
* <p>For example, the data byte at index i is inside the block at counter = i / {@link AesCtrUtil#AES_BLOCK_SIZE}.
* CTR mode computes an IV for this block based on the initial IV (at counter 0) and the provided counter. This allows
* efficient random access to encrypted data. Only the target block needs to be decrypted.
- * <p>This method must be called first. Then the next call to {@link #process(ByteBuffer, ByteBuffer)} will start at the
- * beginning of the block: the first byte of data at input buffer {@link ByteBuffer#position()} must be the first byte
- * of the block.
+ * <p>This method must be called first. Then the next call to {@link #process} will start at the beginning of the
+ * block: the first byte of input data must be the first byte of the block.
*/
void init(long counter);
/**
* Encrypts/decrypts the provided input buffer data and stores the encrypted/decrypted data in an output buffer.
* In CTR mode, encryption and decryption are actually the same operation.
- * <p>Both buffers must be backed by array ({@link ByteBuffer#hasArray()} returns true), and must not share the same
- * array.
* <p>Do not call this method when this {@link AesCtrEncrypter} is not {@link #init(long) initialized}.
* <p>This method takes care of incrementing the CTR counter while encrypting/decrypting the data. It can be called
* repeatedly without calling {@link #init(long)} again. {@link #init(long)} is called only to jump to a given block.
*
- * @param inBuffer Input data from {@link ByteBuffer#position()} (inclusive) to {@link ByteBuffer#limit()} (exclusive).
- * @param outBuffer Output data to be stored at {@link ByteBuffer#position()}. outBuffer {@link ByteBuffer#remaining()}
- * must be greater than or equal to inBuffer {@link ByteBuffer#remaining()}.
+ * @param inBuffer Input data buffer.
+ * @param inOffset Offset (inclusive) of the first byte of input data.
+ * @param length Number of input bytes to process.
+ * @param outBuffer Output data buffer where to put the processed bytes. Its capacity must be greater than or equal
+ * {@code length}.
+ * @param outOffset Output offset (inclusive) where to put the first output byte.
*/
- void process(ByteBuffer inBuffer, ByteBuffer outBuffer);
+ void process(byte[] inBuffer, int inOffset, int length, byte[] outBuffer, int outOffset);
/**
* Clones this {@link AesCtrEncrypter} for efficiency as it clones the internal encryption key and IV.
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrUtil.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrUtil.java
index 7546a9c..63c397a 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrUtil.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/AesCtrUtil.java
@@ -32,41 +32,47 @@ public class AesCtrUtil {
* AES/CTR IV length. It is equal to {@link #AES_BLOCK_SIZE}. It is defined separately mainly for code clarity.
*/
public static final int IV_LENGTH = AES_BLOCK_SIZE;
+ /** CTR counter length. 5 bytes means we can encrypt files up to 2^(5 x 8) x 16 B = 17.5 TB */
+ private static final int COUNTER_LENGTH = 5;
+ private static final long COUNTER_MAX_VALUE = (1L << (COUNTER_LENGTH * Byte.SIZE)) - 1;
/**
* Checks a key for AES. Its length must be either 16, 24 or 32 bytes.
+ * @return true
+ * @throws IllegalArgumentException If the key length is invalid.
*/
- public static void checkAesKey(byte[] key) {
+ public static boolean checkAesKey(byte[] key) {
if (key.length != 16 && key.length != 24 && key.length != 32) {
// AES requires either 128, 192 or 256 bits keys.
throw new IllegalArgumentException("Invalid AES key length; it must be either 128, 192 or 256 bits long");
}
+ return true;
}
/**
- * Checks the CTR counter. It must be positive or null.
+ * Checks the CTR counter. It must be greater than or equal to 0, and less than or equal to
+ * {@link #COUNTER_MAX_VALUE}.
+ * @return true
+ * @throws IllegalArgumentException If the counter is invalid.
*/
- public static void checkCtrCounter(long counter) {
- if (counter < 0) {
- throw new IllegalArgumentException("Illegal counter=" + counter);
+ public static boolean checkCtrCounter(long counter) {
+ if (counter < 0 || counter > COUNTER_MAX_VALUE) {
+ throw new IllegalArgumentException("Invalid counter=" + counter);
}
+ return true;
}
/**
* Generates a random IV for AES/CTR of length {@link #IV_LENGTH}.
*/
public static byte[] generateRandomAesCtrIv(SecureRandom secureRandom) {
- // IV length must be the AES block size.
- // IV must be random for the CTR mode. It starts with counter 0, so it's simply IV.
+ // The IV length must be the AES block size.
+ // For the CTR mode, the IV is composed of a random NONCE (first bytes) and a counter (last bytes).
+ // com.sun.crypto.provider.CounterMode.increment() increments the counter starting from the last byte.
+ byte[] nonce = new byte[IV_LENGTH - COUNTER_LENGTH];
+ secureRandom.nextBytes(nonce);
byte[] iv = new byte[IV_LENGTH];
- do {
- secureRandom.nextBytes(iv);
- // Ensure that we have enough bits left to allow the 8 bytes counter to add with the carry.
- // The high-order byte is at index 0.
- // We check that there is at least one unset bit in the 3 highest bytes. It guarantees
- // that we can add with the carry at least 5 bytes of the counter, which means we handle
- // files of at least 2^(5*8) * 2 * 16 B = 35,000 GB.
- } while (iv[0] == -1 && iv[1] == -1 && iv[2] == -1);
+ System.arraycopy(nonce, 0, iv, 0, nonce.length);
return iv;
}
@@ -74,20 +80,13 @@ public class AesCtrUtil {
* Builds an AES/CTR IV based on the provided counter and an initial IV.
* The built IV is the same as with {@code com.sun.crypto.provider.CounterMode.increment()}.
*/
- public static void buildAesCtrIv(byte[] initialIv, long counter, byte[] iv) {
- assert initialIv.length == IV_LENGTH && iv.length == IV_LENGTH;
- int ivIndex = iv.length;
- int counterIndex = 0;
- int sum = 0;
- while (ivIndex-- > 0) {
- // (sum >>> Byte.SIZE) is the carry for counter addition.
- sum = (initialIv[ivIndex] & 0xff) + (sum >>> Byte.SIZE);
- // Add long counter.
- if (counterIndex++ < 8) {
- sum += (byte) counter & 0xff;
- counter >>>= 8;
- }
- iv[ivIndex] = (byte) sum;
- }
+ public static void buildAesCtrIv(byte[] iv, long counter) {
+ assert iv.length == IV_LENGTH;
+ assert checkCtrCounter(counter);
+ iv[iv.length - 1] = (byte) counter;
+ iv[iv.length - 2] = (byte) (counter >>= 8);
+ iv[iv.length - 3] = (byte) (counter >>= 8);
+ iv[iv.length - 4] = (byte) (counter >>= 8);
+ iv[iv.length - 5] = (byte) (counter >> 8);
}
}
\ No newline at end of file
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/CipherAesCtrEncrypter.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/CipherAesCtrEncrypter.java
index b11a294..2244da5 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/CipherAesCtrEncrypter.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/CipherAesCtrEncrypter.java
@@ -21,7 +21,6 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
-import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
@@ -33,8 +32,8 @@ import static org.apache.solr.encryption.crypto.AesCtrUtil.*;
* {@link AesCtrEncrypter} backed by a {@link javax.crypto.Cipher} with the "AES/CTR/NoPadding" transformation.
* <p>This encrypter loads the internal {@link javax.crypto.CipherSpi} implementation from the classpath, see
* {@link Cipher#getInstance(String)}. It is heavy to create, to initialize and to clone, but the
- * {@link #process(ByteBuffer, ByteBuffer) encryption} is extremely fast thanks to {@code HotSpotIntrinsicCandidate}
- * annotation in com.sun.crypto.provider.CounterMode.
+ * {@link #process encryption} is fast thanks to the {@code HotSpotIntrinsicCandidate} annotation in
+ * {@code com.sun.crypto.provider.CounterMode}.
*/
public class CipherAesCtrEncrypter implements AesCtrEncrypter {
@@ -49,7 +48,6 @@ public class CipherAesCtrEncrypter implements AesCtrEncrypter {
private byte[] iv;
private ReusableIvParameterSpec ivParameterSpec;
private Cipher cipher;
- private long counter;
/**
* @param key The encryption key. It is cloned internally, its content is not modified, and no reference to it is kept.
@@ -68,11 +66,7 @@ public class CipherAesCtrEncrypter implements AesCtrEncrypter {
@Override
public void init(long counter) {
- checkCtrCounter(counter);
- if (counter != this.counter) {
- this.counter = counter;
- buildAesCtrIv(initialIv, counter, iv);
- }
+ buildAesCtrIv(iv, counter);
try {
cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec, SecureRandomProvider.get());
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
@@ -81,13 +75,10 @@ public class CipherAesCtrEncrypter implements AesCtrEncrypter {
}
@Override
- public void process(ByteBuffer inBuffer, ByteBuffer outBuffer) {
+ public void process(byte[] inBuffer, int inOffset, int length, byte[] outBuffer, int outOffset) {
try {
- int inputSize = inBuffer.remaining();
- int numEncryptedBytes = cipher.update(inBuffer, outBuffer);
- if (numEncryptedBytes < inputSize) {
- throw new UnsupportedOperationException(Cipher.class.getSimpleName() + " implementation does not maintain an encryption context; this is not supported");
- }
+ int numEncryptedBytes = cipher.update(inBuffer, inOffset, length, outBuffer, outOffset);
+ assert numEncryptedBytes == length : Cipher.class.getSimpleName() + " implementation does not maintain an encryption context; this is not supported";
} catch (ShortBufferException e) {
throw new RuntimeException(e);
}
@@ -105,7 +96,6 @@ public class CipherAesCtrEncrypter implements AesCtrEncrypter {
clone.iv = initialIv.clone();
clone.ivParameterSpec = new ReusableIvParameterSpec(clone.iv);
clone.cipher = createAesCtrCipher();
- clone.counter = 0;
return clone;
}
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingChannelInputStream.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingChannelInputStream.java
index 496c08c..81631f2 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingChannelInputStream.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingChannelInputStream.java
@@ -44,7 +44,9 @@ public class DecryptingChannelInputStream extends TransactionLog.ChannelFastInpu
private final byte[] iv;
private final AesCtrEncrypter encrypter;
private final ByteBuffer inBuffer;
- private final ByteBuffer outBuffer;
+ private final byte[] outBuffer;
+ private int outPos;
+ private int outSize;
private int padding;
private long filePointer;
@@ -66,6 +68,29 @@ public class DecryptingChannelInputStream extends TransactionLog.ChannelFastInpu
byte[] key,
AesCtrEncrypterFactory factory)
throws IOException {
+ this(channel, offset, position, key, factory, BUFFER_CAPACITY);
+ }
+
+ /**
+ * @param channel The delegate {@link FileChannel} to read and decrypt data from.
+ * @param offset Base offset in the {@link FileChannel}. The IV at the beginning of
+ * the file starts at this offset. {@link #seek(long)} positions are
+ * relative to this base offset + {@link AesCtrUtil#IV_LENGTH}.
+ * @param position Initial read position, relative to the base offset. Setting a positive
+ * position is equivalent to setting a zero position and then calling
+ * {@link #setPosition(long)}.
+ * @param key The encryption key secret. It is cloned internally, its content
+ * is not modified, and no reference to it is kept.
+ * @param factory The factory to use to create one instance of {@link AesCtrEncrypter}.
+ * @param bufferCapacity The encryption buffer capacity. It must be a multiple of {@link AesCtrUtil#AES_BLOCK_SIZE}.
+ */
+ public DecryptingChannelInputStream(FileChannel channel,
+ long offset,
+ long position,
+ byte[] key,
+ AesCtrEncrypterFactory factory,
+ int bufferCapacity)
+ throws IOException {
super(channel, position);
assert offset >= 0;
assert position >= 0;
@@ -73,11 +98,9 @@ public class DecryptingChannelInputStream extends TransactionLog.ChannelFastInpu
iv = new byte[IV_LENGTH];
channel.read(ByteBuffer.wrap(iv, 0, iv.length), offset);
encrypter = factory.create(key, iv);
- inBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
- outBuffer = ByteBuffer.allocate(BUFFER_CAPACITY + AES_BLOCK_SIZE);
- outBuffer.limit(0);
- assert inBuffer.hasArray() && outBuffer.hasArray();
- assert inBuffer.arrayOffset() == 0;
+ assert bufferCapacity % AES_BLOCK_SIZE == 0;
+ inBuffer = ByteBuffer.allocate(bufferCapacity);
+ outBuffer = new byte[bufferCapacity + AES_BLOCK_SIZE];
setPosition(position);
}
@@ -95,20 +118,20 @@ public class DecryptingChannelInputStream extends TransactionLog.ChannelFastInpu
int numDecrypted = 0;
while (length > 0) {
// Transfer decrypted bytes from outBuffer.
- int outRemaining = outBuffer.remaining();
+ int outRemaining = outSize - outPos;
if (outRemaining > 0) {
if (length <= outRemaining) {
- outBuffer.get(target, offset, length);
+ System.arraycopy(outBuffer, outPos, target, offset, length);
+ outPos += length;
numDecrypted += length;
return numDecrypted;
}
- outBuffer.get(target, offset, outRemaining);
+ System.arraycopy(outBuffer, outPos, target, offset, outRemaining);
+ outPos += outRemaining;
numDecrypted += outRemaining;
- assert outBuffer.remaining() == 0;
offset += outRemaining;
length -= outRemaining;
}
-
if (!readToFillBuffer(length)) {
return numDecrypted == 0 ? -1 : numDecrypted;
}
@@ -138,16 +161,13 @@ public class DecryptingChannelInputStream extends TransactionLog.ChannelFastInpu
}
private void decryptBuffer() {
- assert inBuffer.position() > padding : "position=" + inBuffer.position() + ", padding=" + padding;
- inBuffer.flip();
- outBuffer.clear();
- encrypter.process(inBuffer, outBuffer);
- inBuffer.clear();
- outBuffer.flip();
- if (padding > 0) {
- outBuffer.position(padding);
- padding = 0;
- }
+ int inPos = inBuffer.position();
+ assert inPos > padding : "inPos=" + inPos + " padding=" + padding;
+ encrypter.process(inBuffer.array(), 0, inPos, outBuffer, 0);
+ outSize = inPos;
+ inBuffer.position(0);
+ outPos = padding;
+ padding = 0;
}
@Override
@@ -158,11 +178,11 @@ public class DecryptingChannelInputStream extends TransactionLog.ChannelFastInpu
pos = (int) (position - getBufferPos());
} else {
long channelPosition = filePointer - offset - IV_LENGTH;
- long currentPosition = channelPosition - outBuffer.remaining();
+ long currentPosition = channelPosition - (outSize - outPos);
if (position >= currentPosition && position <= channelPosition) {
// The target position is within the buffered output. Just move the output buffer position.
- outBuffer.position(outBuffer.position() + (int) (position - currentPosition));
- assert position == channelPosition - outBuffer.remaining();
+ outPos += (int) (position - currentPosition);
+ assert position == channelPosition - (outSize - outPos);
} else {
setPosition(position);
}
@@ -173,9 +193,7 @@ public class DecryptingChannelInputStream extends TransactionLog.ChannelFastInpu
}
private void setPosition(long position) {
- inBuffer.clear();
- outBuffer.clear();
- outBuffer.limit(0);
+ outPos = outSize = 0;
// Compute the counter by ignoring the IV and the channel offset, if any.
long counter = position / AES_BLOCK_SIZE;
encrypter.init(counter);
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingIndexInput.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingIndexInput.java
index 072ca25..a1a7fe8 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingIndexInput.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingIndexInput.java
@@ -20,7 +20,6 @@ import org.apache.lucene.store.IndexInput;
import java.io.EOFException;
import java.io.IOException;
-import java.nio.ByteBuffer;
import static org.apache.solr.encryption.crypto.AesCtrUtil.*;
@@ -40,7 +39,7 @@ public class DecryptingIndexInput extends IndexInput {
* Must be a multiple of {@link AesCtrUtil#AES_BLOCK_SIZE}.
* Benchmarks showed that 6 x {@link AesCtrUtil#AES_BLOCK_SIZE} is a good buffer size.
*/
- static final int BUFFER_CAPACITY = 6 * AES_BLOCK_SIZE; // 96 B
+ public static final int BUFFER_CAPACITY = 6 * AES_BLOCK_SIZE; // 96 B
static final long AES_BLOCK_SIZE_MOD_MASK = AES_BLOCK_SIZE - 1;
@@ -51,10 +50,12 @@ public class DecryptingIndexInput extends IndexInput {
private final long sliceEnd;
private IndexInput indexInput;
private AesCtrEncrypter encrypter;
- private ByteBuffer inBuffer;
- private ByteBuffer outBuffer;
- private byte[] inArray;
+ private byte[] inBuffer;
+ private byte[] outBuffer;
private byte[] oneByteBuf;
+ private int inPos;
+ private int outPos;
+ private int outSize;
private int padding;
private boolean closed;
@@ -67,13 +68,30 @@ public class DecryptingIndexInput extends IndexInput {
* @param factory The factory to use to create one instance of {@link AesCtrEncrypter}. This instance may be cloned.
*/
public DecryptingIndexInput(IndexInput indexInput, byte[] key, AesCtrEncrypterFactory factory) throws IOException {
+ this(indexInput, key, factory, BUFFER_CAPACITY);
+ }
+
+ /**
+ * @param indexInput The delegate {@link IndexInput} to read and decrypt data from. Its current file pointer may be
+ * greater than or equal to zero, this allows for example the caller to first read some special
+ * encryption header followed by a key id to retrieve the key secret.
+ * @param key The encryption key secret. It is cloned internally, its content is not modified, and no
+ * reference to it is kept.
+ * @param factory The factory to use to create one instance of {@link AesCtrEncrypter}. This instance may be cloned.
+ * @param bufferCapacity The encryption buffer capacity. It must be a multiple of {@link AesCtrUtil#AES_BLOCK_SIZE}.
+ */
+ public DecryptingIndexInput(IndexInput indexInput,
+ byte[] key,
+ AesCtrEncrypterFactory factory,
+ int bufferCapacity) throws IOException {
this("Decrypting " + indexInput.toString(),
- indexInput.getFilePointer() + IV_LENGTH,
- indexInput.getFilePointer() + IV_LENGTH,
- indexInput.length() - indexInput.getFilePointer() - IV_LENGTH,
- false,
- indexInput,
- createEncrypter(indexInput, key, factory));
+ indexInput.getFilePointer() + IV_LENGTH,
+ indexInput.getFilePointer() + IV_LENGTH,
+ indexInput.length() - indexInput.getFilePointer() - IV_LENGTH,
+ false,
+ indexInput,
+ createEncrypter(indexInput, key, factory),
+ bufferCapacity);
}
private DecryptingIndexInput(String resourceDescription,
@@ -82,7 +100,8 @@ public class DecryptingIndexInput extends IndexInput {
long sliceLength,
boolean isClone,
IndexInput indexInput,
- AesCtrEncrypter encrypter) {
+ AesCtrEncrypter encrypter,
+ int bufferCapacity) {
super(resourceDescription);
assert delegateOffset >= 0 && sliceOffset >= 0 && sliceLength >= 0;
this.delegateOffset = delegateOffset;
@@ -92,12 +111,9 @@ public class DecryptingIndexInput extends IndexInput {
this.indexInput = indexInput;
this.encrypter = encrypter;
encrypter.init(0);
- inBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
- outBuffer = ByteBuffer.allocate(BUFFER_CAPACITY + AES_BLOCK_SIZE);
- outBuffer.limit(0);
- assert inBuffer.hasArray() && outBuffer.hasArray();
- assert inBuffer.arrayOffset() == 0;
- inArray = inBuffer.array();
+ assert bufferCapacity % AES_BLOCK_SIZE == 0;
+ inBuffer = new byte[bufferCapacity];
+ outBuffer = new byte[bufferCapacity + AES_BLOCK_SIZE];
oneByteBuf = new byte[1];
}
@@ -132,7 +148,7 @@ public class DecryptingIndexInput extends IndexInput {
* Gets the current internal position in the delegate {@link IndexInput}. It includes IV length.
*/
private long getPosition() {
- return indexInput.getFilePointer() - outBuffer.remaining();
+ return indexInput.getFilePointer() - (outSize - outPos);
}
@Override
@@ -145,11 +161,11 @@ public class DecryptingIndexInput extends IndexInput {
}
long targetPosition = position + sliceOffset;
long delegatePosition = indexInput.getFilePointer();
- long currentPosition = delegatePosition - outBuffer.remaining();
+ long currentPosition = delegatePosition - (outSize - outPos);
if (targetPosition >= currentPosition && targetPosition <= delegatePosition) {
// The target position is within the buffered output. Just move the output buffer position.
- outBuffer.position(outBuffer.position() + (int) (targetPosition - currentPosition));
- assert targetPosition == delegatePosition - outBuffer.remaining();
+ outPos += (int) (targetPosition - currentPosition);
+ assert targetPosition == delegatePosition - (outSize - outPos);
} else {
indexInput.seek(targetPosition);
setPosition(targetPosition);
@@ -157,15 +173,13 @@ public class DecryptingIndexInput extends IndexInput {
}
private void setPosition(long position) {
- inBuffer.clear();
- outBuffer.clear();
- outBuffer.limit(0);
+ outPos = outSize = 0;
// Compute the counter by ignoring the IV and the delegate offset, if any.
long delegatePosition = position - delegateOffset;
long counter = delegatePosition / AES_BLOCK_SIZE;
encrypter.init(counter);
padding = (int) (delegatePosition & AES_BLOCK_SIZE_MOD_MASK);
- inBuffer.position(padding);
+ inPos = padding;
}
/**
@@ -185,8 +199,9 @@ public class DecryptingIndexInput extends IndexInput {
throw new IllegalArgumentException("Slice \"" + sliceDescription + "\" out of bounds (offset=" + offset
+ ", sliceLength=" + length + ", fileLength=" + length() + ") of " + this);
}
- DecryptingIndexInput slice = new DecryptingIndexInput(getFullSliceDescription(sliceDescription),
- delegateOffset, sliceOffset + offset, length, true, indexInput.clone(), encrypter.clone());
+ DecryptingIndexInput slice = new DecryptingIndexInput(
+ getFullSliceDescription(sliceDescription), delegateOffset, sliceOffset + offset, length, true,
+ indexInput.clone(), encrypter.clone(), inBuffer.length);
slice.seek(0);
return slice;
}
@@ -209,18 +224,18 @@ public class DecryptingIndexInput extends IndexInput {
}
while (length > 0) {
// Transfer decrypted bytes from outBuffer.
- int outRemaining = outBuffer.remaining();
+ int outRemaining = outSize - outPos;
if (outRemaining > 0) {
if (length <= outRemaining) {
- outBuffer.get(b, offset, length);
+ System.arraycopy(outBuffer, outPos, b, offset, length);
+ outPos += length;
return;
}
- outBuffer.get(b, offset, outRemaining);
- assert outBuffer.remaining() == 0;
+ System.arraycopy(outBuffer, outPos, b, offset, outRemaining);
+ outPos += outRemaining;
offset += outRemaining;
length -= outRemaining;
}
-
readToFillBuffer(length);
decryptBuffer();
}
@@ -228,26 +243,21 @@ public class DecryptingIndexInput extends IndexInput {
private void readToFillBuffer(int length) throws IOException {
assert length > 0;
- int inRemaining = inBuffer.remaining();
+ int inRemaining = inBuffer.length - inPos;
if (inRemaining > 0) {
- int position = inBuffer.position();
int numBytesToRead = Math.min(inRemaining, length);
- indexInput.readBytes(inArray, position, numBytesToRead);
- inBuffer.position(position + numBytesToRead);
+ indexInput.readBytes(inBuffer, inPos, numBytesToRead);
+ inPos += numBytesToRead;
}
}
private void decryptBuffer() {
- assert inBuffer.position() > padding : "position=" + inBuffer.position() + ", padding=" + padding;
- inBuffer.flip();
- outBuffer.clear();
- encrypter.process(inBuffer, outBuffer);
- inBuffer.clear();
- outBuffer.flip();
- if (padding > 0) {
- outBuffer.position(padding);
- padding = 0;
- }
+ assert inPos > padding : "inPos=" + inPos + " padding=" + padding;
+ encrypter.process(inBuffer, 0, inPos, outBuffer, 0);
+ outSize = inPos;
+ inPos = 0;
+ outPos = padding;
+ padding = 0;
}
@Override
@@ -257,9 +267,8 @@ public class DecryptingIndexInput extends IndexInput {
clone.indexInput = indexInput.clone();
assert clone.indexInput.getFilePointer() == indexInput.getFilePointer();
clone.encrypter = encrypter.clone();
- clone.inBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
- clone.outBuffer = ByteBuffer.allocate(BUFFER_CAPACITY + AES_BLOCK_SIZE);
- clone.inArray = clone.inBuffer.array();
+ clone.inBuffer = new byte[inBuffer.length];
+ clone.outBuffer = new byte[outBuffer.length];
clone.oneByteBuf = new byte[1];
// The clone must be initialized.
clone.setPosition(getPosition());
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingInputStream.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingInputStream.java
index b94982f..9c52e7c 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingInputStream.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/DecryptingInputStream.java
@@ -18,7 +18,6 @@ package org.apache.solr.encryption.crypto;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.ByteBuffer;
import static org.apache.solr.encryption.crypto.AesCtrUtil.AES_BLOCK_SIZE;
import static org.apache.solr.encryption.crypto.AesCtrUtil.IV_LENGTH;
@@ -40,10 +39,12 @@ public class DecryptingInputStream extends InputStream {
private final InputStream inputStream;
private final byte[] iv;
private final AesCtrEncrypter encrypter;
- private final ByteBuffer inBuffer;
- private final ByteBuffer outBuffer;
- private final byte[] inArray;
+ private final byte[] inBuffer;
+ private final byte[] outBuffer;
private final byte[] oneByteBuf;
+ private int inPos;
+ private int outPos;
+ private int outSize;
private int padding;
private boolean closed;
@@ -75,16 +76,35 @@ public class DecryptingInputStream extends InputStream {
byte[] key,
AesCtrEncrypterFactory factory)
throws IOException {
+ this(inputStream, position, iv, key, factory, BUFFER_CAPACITY);
+ }
+
+ /**
+ * @param inputStream The delegate {@link InputStream} to read and decrypt data from.
+ * @param position The position in the input stream. A non-zero position means the
+ * input skips the beginning of the file, in this case the iv should
+ * be provided and not null.
+ * @param iv The IV to use (not read) if the position is greater than zero;
+ * or null to read it at the beginning of the input.
+ * @param key The encryption key secret. It is cloned internally, its content
+ * is not modified, and no reference to it is kept.
+ * @param factory The factory to use to create one instance of {@link AesCtrEncrypter}.
+ * @param bufferCapacity The encryption buffer capacity. It must be a multiple of {@link AesCtrUtil#AES_BLOCK_SIZE}.
+ */
+ public DecryptingInputStream(InputStream inputStream,
+ long position,
+ byte[] iv,
+ byte[] key,
+ AesCtrEncrypterFactory factory,
+ int bufferCapacity)
+ throws IOException {
if (position < 0) {
throw new IllegalArgumentException("Invalid position " + position);
}
this.inputStream = inputStream;
- inBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
- outBuffer = ByteBuffer.allocate(BUFFER_CAPACITY + AES_BLOCK_SIZE);
- outBuffer.limit(0);
- assert inBuffer.hasArray() && outBuffer.hasArray();
- assert inBuffer.arrayOffset() == 0;
- inArray = inBuffer.array();
+ assert bufferCapacity % AES_BLOCK_SIZE == 0;
+ inBuffer = new byte[bufferCapacity];
+ outBuffer = new byte[bufferCapacity + AES_BLOCK_SIZE];
oneByteBuf = new byte[1];
long counter;
if (position == 0) {
@@ -100,7 +120,7 @@ public class DecryptingInputStream extends InputStream {
} else {
counter = position / AES_BLOCK_SIZE;
padding = (int) (position & AES_BLOCK_SIZE_MOD_MASK);
- inBuffer.position(padding);
+ inPos = padding;
}
this.iv = iv;
encrypter = factory.create(key, iv);
@@ -138,20 +158,20 @@ public class DecryptingInputStream extends InputStream {
int numDecrypted = 0;
while (length > 0) {
// Transfer decrypted bytes from outBuffer.
- int outRemaining = outBuffer.remaining();
+ int outRemaining = outSize - outPos;
if (outRemaining > 0) {
if (length <= outRemaining) {
- outBuffer.get(b, offset, length);
+ System.arraycopy(outBuffer, outPos, b, offset, length);
+ outPos += length;
numDecrypted += length;
return numDecrypted;
}
- outBuffer.get(b, offset, outRemaining);
+ System.arraycopy(outBuffer, outPos, b, offset, outRemaining);
+ outPos += outRemaining;
numDecrypted += outRemaining;
- assert outBuffer.remaining() == 0;
offset += outRemaining;
length -= outRemaining;
}
-
if (!readToFillBuffer(length)) {
return numDecrypted == 0 ? -1 : numDecrypted;
}
@@ -162,29 +182,24 @@ public class DecryptingInputStream extends InputStream {
private boolean readToFillBuffer(int length) throws IOException {
assert length > 0;
- int inRemaining = inBuffer.remaining();
+ int inRemaining = inBuffer.length - inPos;
if (inRemaining > 0) {
- int position = inBuffer.position();
int numBytesToRead = Math.min(inRemaining, length);
- int n = inputStream.read(inArray, position, numBytesToRead);
+ int n = inputStream.read(inBuffer, inPos, numBytesToRead);
if (n == -1) {
return false;
}
- inBuffer.position(position + n);
+ inPos += n;
}
return true;
}
private void decryptBuffer() {
- assert inBuffer.position() > padding : "position=" + inBuffer.position() + ", padding=" + padding;
- inBuffer.flip();
- outBuffer.clear();
- encrypter.process(inBuffer, outBuffer);
- inBuffer.clear();
- outBuffer.flip();
- if (padding > 0) {
- outBuffer.position(padding);
- padding = 0;
- }
+ assert inPos > padding : "inPos=" + inPos + " padding=" + padding;
+ encrypter.process(inBuffer, 0, inPos, outBuffer, 0);
+ outSize = inPos;
+ inPos = 0;
+ outPos = padding;
+ padding = 0;
}
}
\ No newline at end of file
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingIndexOutput.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingIndexOutput.java
index 7c3ccc2..7e18770 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingIndexOutput.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingIndexOutput.java
@@ -20,7 +20,6 @@ import org.apache.lucene.store.BufferedChecksum;
import org.apache.lucene.store.IndexOutput;
import java.io.IOException;
-import java.nio.ByteBuffer;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
@@ -42,15 +41,15 @@ public class EncryptingIndexOutput extends IndexOutput {
/**
* Must be a multiple of {@link AesCtrUtil#AES_BLOCK_SIZE}.
*/
- static final int BUFFER_CAPACITY = 64 * AES_BLOCK_SIZE; // 1024
+ public static final int BUFFER_CAPACITY = 64 * AES_BLOCK_SIZE; // 1024
private final IndexOutput indexOutput;
private final AesCtrEncrypter encrypter;
- private final ByteBuffer inBuffer;
- private final ByteBuffer outBuffer;
- private final byte[] outArray;
+ private final byte[] inBuffer;
+ private final byte[] outBuffer;
private final byte[] oneByteBuf;
private final Checksum clearChecksum;
+ private int inSize;
private long filePointer;
private boolean closed;
@@ -62,7 +61,26 @@ public class EncryptingIndexOutput extends IndexOutput {
* reference to it is kept.
* @param factory The factory to use to create one instance of {@link AesCtrEncrypter}. This instance may be cloned.
*/
- public EncryptingIndexOutput(IndexOutput indexOutput, byte[] key, AesCtrEncrypterFactory factory) throws IOException {
+ public EncryptingIndexOutput(IndexOutput indexOutput, byte[] key, AesCtrEncrypterFactory factory)
+ throws IOException {
+ this(indexOutput, key, factory, BUFFER_CAPACITY);
+ }
+
+ /**
+ * @param indexOutput The delegate {@link IndexOutput} to write encrypted data to. Its current file pointer may be
+ * greater than or equal to zero, this allows for example the caller to first write some special
+ * encryption header followed by a key id, to identify the key secret used to encrypt.
+ * @param key The encryption key secret. It is cloned internally, its content is not modified, and no
+ * reference to it is kept.
+ * @param factory The factory to use to create one instance of {@link AesCtrEncrypter}. This instance may be
+ * cloned.
+ * @param bufferCapacity The encryption buffer capacity. It must be a multiple of {@link AesCtrUtil#AES_BLOCK_SIZE}.
+ */
+ public EncryptingIndexOutput(IndexOutput indexOutput,
+ byte[] key,
+ AesCtrEncrypterFactory factory,
+ int bufferCapacity)
+ throws IOException {
super("Encrypting " + indexOutput.toString(), indexOutput.getName());
this.indexOutput = indexOutput;
@@ -72,14 +90,10 @@ public class EncryptingIndexOutput extends IndexOutput {
// IV is written at the beginning of the index output. It's public.
// Even if the delegate indexOutput is positioned after the initial IV, this index output file pointer is 0 initially.
indexOutput.writeBytes(iv, 0, iv.length);
-
- inBuffer = ByteBuffer.allocate(getBufferCapacity());
- outBuffer = ByteBuffer.allocate(getBufferCapacity() + AES_BLOCK_SIZE);
- assert inBuffer.hasArray() && outBuffer.hasArray();
- assert outBuffer.arrayOffset() == 0;
- outArray = outBuffer.array();
+ assert bufferCapacity % AES_BLOCK_SIZE == 0;
+ inBuffer = new byte[bufferCapacity];
+ outBuffer = new byte[bufferCapacity];
oneByteBuf = new byte[1];
-
// Compute the checksum to skip the initial IV, because an external checksum checker will not see it.
clearChecksum = new BufferedChecksum(new CRC32());
}
@@ -91,19 +105,12 @@ public class EncryptingIndexOutput extends IndexOutput {
return generateRandomAesCtrIv(SecureRandomProvider.get());
}
- /**
- * Gets the buffer capacity. It must be a multiple of {@link AesCtrUtil#AES_BLOCK_SIZE}.
- */
- protected int getBufferCapacity() {
- return BUFFER_CAPACITY;
- }
-
@Override
public void close() throws IOException {
if (!closed) {
closed = true;
try {
- if (inBuffer.position() != 0) {
+ if (inSize != 0) {
encryptBufferAndWrite();
}
} finally {
@@ -139,12 +146,14 @@ public class EncryptingIndexOutput extends IndexOutput {
clearChecksum.update(b, offset, length);
filePointer += length;
while (length > 0) {
- int remaining = inBuffer.remaining();
+ int remaining = inBuffer.length - inSize;
if (length < remaining) {
- inBuffer.put(b, offset, length);
+ System.arraycopy(b, offset, inBuffer, inSize, length);
+ inSize += length;
break;
} else {
- inBuffer.put(b, offset, remaining);
+ System.arraycopy(b, offset, inBuffer, inSize, remaining);
+ inSize += remaining;
offset += remaining;
length -= remaining;
encryptBufferAndWrite();
@@ -153,12 +162,9 @@ public class EncryptingIndexOutput extends IndexOutput {
}
private void encryptBufferAndWrite() throws IOException {
- assert inBuffer.position() != 0;
- inBuffer.flip();
- outBuffer.clear();
- encrypter.process(inBuffer, outBuffer);
- inBuffer.clear();
- outBuffer.flip();
- indexOutput.writeBytes(outArray, 0, outBuffer.limit());
+ assert inSize > 0;
+ encrypter.process(inBuffer, 0, inSize, outBuffer, 0);
+ indexOutput.writeBytes(outBuffer, 0, inSize);
+ inSize = 0;
}
}
\ No newline at end of file
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingOutputStream.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingOutputStream.java
index b57086b..d8554ed 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingOutputStream.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/EncryptingOutputStream.java
@@ -18,7 +18,6 @@ package org.apache.solr.encryption.crypto;
import java.io.IOException;
import java.io.OutputStream;
-import java.nio.ByteBuffer;
import static org.apache.solr.encryption.crypto.AesCtrUtil.AES_BLOCK_SIZE;
import static org.apache.solr.encryption.crypto.AesCtrUtil.generateRandomAesCtrIv;
@@ -40,10 +39,10 @@ public class EncryptingOutputStream extends OutputStream {
private final OutputStream outputStream;
private final byte[] iv;
private final AesCtrEncrypter encrypter;
- private final ByteBuffer inBuffer;
- private final ByteBuffer outBuffer;
- private final byte[] outArray;
+ private final byte[] inBuffer;
+ private final byte[] outBuffer;
private final byte[] oneByteBuf;
+ private int inSize;
private int padding;
private boolean closed;
@@ -76,15 +75,35 @@ public class EncryptingOutputStream extends OutputStream {
byte[] key,
AesCtrEncrypterFactory factory)
throws IOException {
+ this(outputStream, position, iv, key, factory, BUFFER_CAPACITY);
+ }
+
+ /**
+ * @param outputStream The delegate {@link OutputStream} to write encrypted data to.
+ * @param position The position in the output stream. A non-zero position means the
+ * output is reopened to append more data, in this case the iv should
+ * be provided and not null.
+ * @param iv The IV to use (not written) if the position is greater than zero;
+ * or null to generate a random one and write it at the beginning of
+ * the output.
+ * @param key The encryption key secret. It is cloned internally, its content
+ * is not modified, and no reference to it is kept.
+ * @param factory The factory to use to create one instance of {@link AesCtrEncrypter}.
+ */
+ public EncryptingOutputStream(OutputStream outputStream,
+ long position,
+ byte[] iv,
+ byte[] key,
+ AesCtrEncrypterFactory factory,
+ int bufferCapacity)
+ throws IOException {
if (position < 0) {
throw new IllegalArgumentException("Invalid position " + position);
}
this.outputStream = outputStream;
- inBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
- outBuffer = ByteBuffer.allocate(BUFFER_CAPACITY + AES_BLOCK_SIZE);
- assert inBuffer.hasArray() && outBuffer.hasArray();
- assert outBuffer.arrayOffset() == 0;
- outArray = outBuffer.array();
+ assert bufferCapacity % AES_BLOCK_SIZE == 0;
+ inBuffer = new byte[bufferCapacity];
+ outBuffer = new byte[bufferCapacity + AES_BLOCK_SIZE];
oneByteBuf = new byte[1];
long counter;
if (position == 0) {
@@ -97,7 +116,7 @@ public class EncryptingOutputStream extends OutputStream {
} else {
counter = position / AES_BLOCK_SIZE;
padding = (int) (position & (AES_BLOCK_SIZE - 1));
- inBuffer.position(padding);
+ inSize = padding;
}
this.iv = iv;
encrypter = factory.create(key, iv);
@@ -120,7 +139,7 @@ public class EncryptingOutputStream extends OutputStream {
@Override
public void flush() throws IOException {
- if (inBuffer.position() > padding) {
+ if (inSize > padding) {
encryptBufferAndWrite();
}
}
@@ -151,12 +170,14 @@ public class EncryptingOutputStream extends OutputStream {
+ ", arrayLength=" + b.length + ")");
}
while (length > 0) {
- int remaining = inBuffer.remaining();
+ int remaining = inBuffer.length - inSize;
if (length < remaining) {
- inBuffer.put(b, offset, length);
+ System.arraycopy(b, offset, inBuffer, inSize, length);
+ inSize += length;
break;
} else {
- inBuffer.put(b, offset, remaining);
+ System.arraycopy(b, offset, inBuffer, inSize, remaining);
+ inSize += remaining;
offset += remaining;
length -= remaining;
encryptBufferAndWrite();
@@ -165,13 +186,10 @@ public class EncryptingOutputStream extends OutputStream {
}
private void encryptBufferAndWrite() throws IOException {
- assert inBuffer.position() > padding : "position=" + inBuffer.position() + ", padding=" + padding;
- inBuffer.flip();
- outBuffer.clear();
- encrypter.process(inBuffer, outBuffer);
- inBuffer.clear();
- outBuffer.flip();
- outputStream.write(outArray, padding, outBuffer.limit() - padding);
+ assert inSize > padding : "inSize=" + inSize + " padding=" + padding;
+ encrypter.process(inBuffer, 0, inSize, outBuffer, 0);
+ outputStream.write(outBuffer, padding, inSize - padding);
+ inSize = 0;
padding = 0;
}
}
\ No newline at end of file
diff --git a/encryption/src/main/java/org/apache/solr/encryption/crypto/LightAesCtrEncrypter.java b/encryption/src/main/java/org/apache/solr/encryption/crypto/LightAesCtrEncrypter.java
index 76b4c15..4aae7ad 100644
--- a/encryption/src/main/java/org/apache/solr/encryption/crypto/LightAesCtrEncrypter.java
+++ b/encryption/src/main/java/org/apache/solr/encryption/crypto/LightAesCtrEncrypter.java
@@ -19,7 +19,6 @@ package org.apache.solr.encryption.crypto;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -100,8 +99,7 @@ public class LightAesCtrEncrypter implements AesCtrEncrypter {
@Override
public void init(long counter) {
- checkCtrCounter(counter);
- buildAesCtrIv(initialIv, counter, iv);
+ buildAesCtrIv(iv, counter);
try {
COUNTER_MODE_IV_FIELD.set(counterMode, iv);
COUNTER_MODE_RESET_METHOD.invoke(counterMode);
@@ -111,21 +109,13 @@ public class LightAesCtrEncrypter implements AesCtrEncrypter {
}
@Override
- public void process(ByteBuffer inBuffer, ByteBuffer outBuffer) {
- assert inBuffer.array() != outBuffer.array() : "Input and output buffers must not be backed by the same array";
- int length = inBuffer.remaining();
- if (length > outBuffer.remaining()) {
- throw new IllegalArgumentException("Output buffer does not have enough remaining space (needs " + length + " B)");
- }
- int outPos = outBuffer.position();
+ public void process(byte[] inBuffer, int inOffset, int length, byte[] outBuffer, int outOffset) {
+ assert outOffset + length <= outBuffer.length;
try {
- COUNTER_MODE_CRYPT_METHOD.invoke(counterMode, inBuffer.array(), inBuffer.arrayOffset() + inBuffer.position(),
- length, outBuffer.array(), outBuffer.arrayOffset() + outPos);
+ COUNTER_MODE_CRYPT_METHOD.invoke(counterMode, inBuffer, inOffset, length, outBuffer, outOffset);
} catch (Exception e) {
throw new RuntimeException(e);
}
- inBuffer.position(inBuffer.limit());
- outBuffer.position(outPos + length);
}
@Override
diff --git a/encryption/src/test/java/org/apache/solr/encryption/crypto/AesCtrEncrypterTest.java b/encryption/src/test/java/org/apache/solr/encryption/crypto/AesCtrEncrypterTest.java
index ef7807d..c8f2809 100644
--- a/encryption/src/test/java/org/apache/solr/encryption/crypto/AesCtrEncrypterTest.java
+++ b/encryption/src/test/java/org/apache/solr/encryption/crypto/AesCtrEncrypterTest.java
@@ -16,13 +16,11 @@
*/
package org.apache.solr.encryption.crypto;
-import java.nio.ByteBuffer;
-
import com.carrotsearch.randomizedtesting.RandomizedTest;
import org.junit.Test;
-import static junit.framework.TestCase.assertEquals;
import static org.apache.solr.encryption.crypto.AesCtrUtil.*;
+import static org.junit.Assert.assertArrayEquals;
/**
* Tests {@link AesCtrEncrypter} implementations.
@@ -35,21 +33,25 @@ public class AesCtrEncrypterTest extends RandomizedTest {
*/
@Test
public void testEncryptionDecryption() {
- for (int i = 0; i < 100; i++) {
- ByteBuffer clearData = generateRandomData(randomIntBetween(5000, 10000));
- byte[] key = randomBytesOfLength(randomIntBetween(2, 4) * 8);
- byte[] iv = generateRandomAesCtrIv(SecureRandomProvider.get());
- AesCtrEncrypter encrypter1 = encrypterFactory().create(key, iv);
- AesCtrEncrypter encrypter2 = encrypterFactory().create(key, iv);
+ for (int i = 0; i < 3000; i++) {
+ try {
+ byte[] clearData = generateRandomBytes(randomIntBetween(5000, 10000));
+ byte[] key = randomBytesOfLength(randomIntBetween(2, 4) * 8);
+ byte[] iv = generateRandomAesCtrIv(SecureRandomProvider.get());
+ AesCtrEncrypter encrypter1 = encrypterFactory().create(key, iv);
+ AesCtrEncrypter encrypter2 = encrypterFactory().create(key, iv);
- ByteBuffer encryptedDataLight = crypt(clearData, encrypter1);
- ByteBuffer encryptedDataCipher = crypt(clearData, encrypter2);
- assertEquals(encryptedDataCipher, encryptedDataLight);
+ byte[] encryptedDataLight = crypt(clearData, encrypter1);
+ byte[] encryptedDataCipher = crypt(clearData, encrypter2);
+ assertArrayEquals(encryptedDataCipher, encryptedDataLight);
- ByteBuffer decryptedData = crypt(encryptedDataLight, encrypter1);
- assertEquals(clearData, decryptedData);
- decryptedData = crypt(encryptedDataLight, encrypter2);
- assertEquals(clearData, decryptedData);
+ byte[] decryptedData = crypt(encryptedDataLight, encrypter1);
+ assertArrayEquals(clearData, decryptedData);
+ decryptedData = crypt(encryptedDataLight, encrypter2);
+ assertArrayEquals(clearData, decryptedData);
+ } catch (RuntimeException e) {
+ throw new RuntimeException("Exception at i=" + i, e);
+ }
}
}
@@ -60,29 +62,28 @@ public class AesCtrEncrypterTest extends RandomizedTest {
return CipherAesCtrEncrypter.FACTORY;
}
- private static ByteBuffer generateRandomData(int numBytes) {
- ByteBuffer buffer = ByteBuffer.allocate(numBytes);
+ private static byte[] generateRandomBytes(int numBytes) {
+ byte[] b = new byte[numBytes];
+ // Random.nextBytes(byte[]) does not produce good enough randomness here,
+ // it has a bias to produce 0 and -1 bytes.
for (int i = 0; i < numBytes; i++) {
- buffer.put((byte) randomInt());
+ b[i] = (byte) randomInt();
}
- buffer.position(0);
- return buffer;
+ return b;
}
- private ByteBuffer crypt(ByteBuffer inputBuffer, AesCtrEncrypter encrypter) {
+ private byte[] crypt(byte[] inputBuffer, AesCtrEncrypter encrypter) {
encrypter = randomClone(encrypter);
encrypter.init(0);
- int inputInitialPosition = inputBuffer.position();
- ByteBuffer outputBuffer = ByteBuffer.allocate(inputBuffer.capacity());
- while (inputBuffer.remaining() > 0) {
- int length = Math.min(randomIntBetween(0, 50) + 1, inputBuffer.remaining());
- ByteBuffer inputSlice = inputBuffer.slice();
- inputSlice.limit(inputSlice.position() + length);
- encrypter.process(inputSlice, outputBuffer);
- inputBuffer.position(inputBuffer.position() + length);
+ byte[] outputBuffer = new byte[inputBuffer.length];
+ int inIndex = 0;
+ int outIndex = 0;
+ while (inIndex < inputBuffer.length) {
+ int length = Math.min(randomIntBetween(0, 50) + 1, inputBuffer.length - inIndex);
+ encrypter.process(inputBuffer, inIndex, length, outputBuffer, outIndex);
+ inIndex += length;
+ outIndex += length;
}
- inputBuffer.position(inputInitialPosition);
- outputBuffer.position(0);
return outputBuffer;
}
diff --git a/encryption/src/test/java/org/apache/solr/encryption/crypto/DecryptingIndexInputTest.java b/encryption/src/test/java/org/apache/solr/encryption/crypto/DecryptingIndexInputTest.java
index 393f8c0..7caf30b 100644
--- a/encryption/src/test/java/org/apache/solr/encryption/crypto/DecryptingIndexInputTest.java
+++ b/encryption/src/test/java/org/apache/solr/encryption/crypto/DecryptingIndexInputTest.java
@@ -122,16 +122,17 @@ public class DecryptingIndexInputTest extends RandomizedTest {
long outputLength = indexOutput.getFilePointer();
indexOutput.close();
- IndexInput indexInput = createDecryptingIndexInput(dataOutput, offset).slice("Test", prefix.length, outputLength - prefix.length - suffix.length);
+ try (IndexInput indexInput = createDecryptingIndexInput(dataOutput, offset)) {
+ IndexInput sliceInput = indexInput.slice("Test", prefix.length, outputLength - prefix.length - suffix.length);
- assertEquals(0, indexInput.getFilePointer());
- assertEquals(outputLength - prefix.length - suffix.length, indexInput.length());
- for (IOConsumer<DataInput> c : reply) {
- c.accept(indexInput);
- }
+ assertEquals(0, sliceInput.getFilePointer());
+ assertEquals(outputLength - prefix.length - suffix.length, sliceInput.length());
+ for (IOConsumer<DataInput> c : reply) {
+ c.accept(sliceInput);
+ }
- LuceneTestCase.expectThrows(EOFException.class, indexInput::readByte);
- indexInput.close();
+ LuceneTestCase.expectThrows(EOFException.class, sliceInput::readByte);
+ }
}
}
@@ -173,31 +174,32 @@ public class DecryptingIndexInputTest extends RandomizedTest {
long outputLength = indexOutput.getFilePointer();
indexOutput.close();
- IndexInput indexInput = createDecryptingIndexInput(dataOutput, offset).slice("Test", prefix.length, outputLength - prefix.length);
+ try (IndexInput indexInput = createDecryptingIndexInput(dataOutput, offset)) {
+ IndexInput sliceInput = indexInput.slice("Test", prefix.length, outputLength - prefix.length);
- indexInput.seek(0);
- for (IOConsumer<DataInput> c : reply) {
- c.accept(indexInput);
- }
+ sliceInput.seek(0);
+ for (IOConsumer<DataInput> c : reply) {
+ c.accept(sliceInput);
+ }
- indexInput.seek(0);
- for (IOConsumer<DataInput> c : reply) {
- c.accept(indexInput);
- }
+ sliceInput.seek(0);
+ for (IOConsumer<DataInput> c : reply) {
+ c.accept(sliceInput);
+ }
- byte[] clearData = clearDataOutput.toArrayCopy();
- clearData = ArrayUtil.copyOfSubArray(clearData, prefix.length, clearData.length);
+ byte[] clearData = clearDataOutput.toArrayCopy();
+ clearData = ArrayUtil.copyOfSubArray(clearData, prefix.length, clearData.length);
- for (int i = 0; i < 1000; i++) {
- int offs = randomIntBetween(0, clearData.length - 1);
- indexInput.seek(offs);
- assertEquals(offs, indexInput.getFilePointer());
- assertEquals("reps=" + reps + " i=" + i + ", offs=" + offs, clearData[offs], indexInput.readByte());
+ for (int i = 0; i < 1000; i++) {
+ int offs = randomIntBetween(0, clearData.length - 1);
+ sliceInput.seek(offs);
+ assertEquals(offs, sliceInput.getFilePointer());
+ assertEquals("reps=" + reps + " i=" + i + ", offs=" + offs, clearData[offs], sliceInput.readByte());
+ }
+ sliceInput.seek(sliceInput.length());
+ assertEquals(sliceInput.length(), sliceInput.getFilePointer());
+ LuceneTestCase.expectThrows(EOFException.class, sliceInput::readByte);
}
- indexInput.seek(indexInput.length());
- assertEquals(indexInput.length(), indexInput.getFilePointer());
- LuceneTestCase.expectThrows(EOFException.class, indexInput::readByte);
- indexInput.close();
}
}
diff --git a/encryption/src/test/java/org/apache/solr/encryption/crypto/EncryptingIndexOutputTest.java b/encryption/src/test/java/org/apache/solr/encryption/crypto/EncryptingIndexOutputTest.java
index 2de88e4..edb6849 100644
--- a/encryption/src/test/java/org/apache/solr/encryption/crypto/EncryptingIndexOutputTest.java
+++ b/encryption/src/test/java/org/apache/solr/encryption/crypto/EncryptingIndexOutputTest.java
@@ -61,13 +61,8 @@ public class EncryptingIndexOutputTest extends BaseDataOutputTestCase<Encrypting
OutputStreamIndexOutput delegateIndexOutput = new OutputStreamIndexOutput("test", "test", baos, 10);
byte[] key = new byte[32];
Arrays.fill(key, (byte) 1);
- EncryptingIndexOutput indexOutput = new EncryptingIndexOutput(delegateIndexOutput, key, encrypterFactory()) {
- @Override
- protected int getBufferCapacity() {
- // Reduce the buffer capacity to make sure we often write to the index output.
- return AesCtrUtil.AES_BLOCK_SIZE;
- }
- };
+ // Reduce the buffer capacity to make sure we often write to the index output.
+ EncryptingIndexOutput indexOutput = new EncryptingIndexOutput(delegateIndexOutput, key, encrypterFactory(), AesCtrUtil.AES_BLOCK_SIZE);
indexOutput.writeByte((byte) 3);
// Check same file pointer.
assertEquals(1, indexOutput.getFilePointer());