Skip to content

Commit adb0a42

Browse files
authored
fix: do not truncate encrypted streams when offset is greater than zero (#2113)
* fix: do not truncate encrypted streams when offset is greater than zero
1 parent 86d5b62 commit adb0a42

File tree

2 files changed

+105
-8
lines changed

2 files changed

+105
-8
lines changed

src/main/java/com/amazonaws/encryptionsdk/internal/FrameEncryptionHandler.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public FrameEncryptionHandler(
8181
* </ol>
8282
*
8383
* @param in the input byte array.
84-
* @param inOff the offset into the in array where the data to be encrypted starts.
85-
* @param inLen the number of bytes to be encrypted.
84+
* @param off the offset into the in array where the data to be encrypted starts.
85+
* @param len the number of bytes to be encrypted.
8686
* @param out the output buffer the encrypted bytes go into.
8787
* @param outOff the offset into the output byte array the encrypted data starts at.
8888
* @return the number of bytes written to out and processed
@@ -95,13 +95,13 @@ public ProcessingSummary processBytes(
9595
int actualOutLen = 0;
9696

9797
int size = len;
98-
int offset = off;
98+
int processedBytes = 0;
9999
while (size > 0) {
100100
final int currentFrameCapacity = frameSize_ - bytesToFrameLen_;
101101
// bind size to the capacity of the current frame
102102
size = Math.min(currentFrameCapacity, size);
103103

104-
System.arraycopy(in, offset, bytesToFrame_, bytesToFrameLen_, size);
104+
System.arraycopy(in, off + processedBytes, bytesToFrame_, bytesToFrameLen_, size);
105105
bytesToFrameLen_ += size;
106106

107107
// check if there is enough bytes to create a frame
@@ -113,10 +113,10 @@ public ProcessingSummary processBytes(
113113
bytesToFrameLen_ = 0;
114114
}
115115

116-
// update offset by the size of bytes being encrypted.
117-
offset += size;
118-
// update size to the remaining bytes starting at offset.
119-
size = len - offset;
116+
// add the size of this frame to processedBytes
117+
processedBytes += size;
118+
// remaining size is original len minus processedBytes
119+
size = len - processedBytes;
120120
}
121121

122122
return new ProcessingSummary(actualOutLen, len);

src/test/java/com/amazonaws/encryptionsdk/internal/FrameEncryptionHandlerTest.java

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,29 @@
1919

2020
import com.amazonaws.encryptionsdk.AwsCrypto;
2121
import com.amazonaws.encryptionsdk.CryptoAlgorithm;
22+
import com.amazonaws.encryptionsdk.CryptoInputStream;
23+
import com.amazonaws.encryptionsdk.CryptoOutputStream;
24+
import com.amazonaws.encryptionsdk.CryptoResult;
2225
import com.amazonaws.encryptionsdk.TestUtils;
2326
import com.amazonaws.encryptionsdk.model.CipherFrameHeaders;
27+
import java.io.ByteArrayInputStream;
28+
import java.io.ByteArrayOutputStream;
2429
import java.lang.reflect.Field;
30+
import java.nio.ByteBuffer;
31+
import java.nio.charset.StandardCharsets;
32+
import java.security.SecureRandom;
33+
import java.util.Collections;
2534
import javax.crypto.SecretKey;
2635
import javax.crypto.spec.SecretKeySpec;
2736
import org.bouncycastle.util.encoders.Hex;
2837
import org.junit.Before;
2938
import org.junit.Test;
39+
import software.amazon.awssdk.utils.StringUtils;
40+
import software.amazon.cryptography.materialproviders.IKeyring;
41+
import software.amazon.cryptography.materialproviders.MaterialProviders;
42+
import software.amazon.cryptography.materialproviders.model.AesWrappingAlg;
43+
import software.amazon.cryptography.materialproviders.model.CreateRawAesKeyringInput;
44+
import software.amazon.cryptography.materialproviders.model.MaterialProvidersConfig;
3045

3146
public class FrameEncryptionHandlerTest {
3247
private final CryptoAlgorithm cryptoAlgorithm_ = TestUtils.DEFAULT_TEST_CRYPTO_ALG;
@@ -117,4 +132,86 @@ private void assertHeaderNonce(byte[] expectedNonce, byte[] buf) {
117132
private void generateTestBlock(byte[] buf) {
118133
frameEncryptionHandler_.processBytes(new byte[frameSize_], 0, frameSize_, buf, 0);
119134
}
135+
136+
/**
137+
* This isn't a unit test, but it reproduces a bug in the FrameEncryptionHandler where the stream
138+
* would be truncated when the offset is >0
139+
*
140+
* @throws Exception
141+
*/
142+
@Test
143+
public void testStreamTruncation() throws Exception {
144+
// Initialize AES key and keyring
145+
SecureRandom rnd = new SecureRandom();
146+
byte[] rawKey = new byte[16];
147+
rnd.nextBytes(rawKey);
148+
SecretKeySpec cryptoKey = new SecretKeySpec(rawKey, "AES");
149+
MaterialProviders materialProviders =
150+
MaterialProviders.builder()
151+
.MaterialProvidersConfig(MaterialProvidersConfig.builder().build())
152+
.build();
153+
CreateRawAesKeyringInput keyringInput =
154+
CreateRawAesKeyringInput.builder()
155+
.wrappingKey(ByteBuffer.wrap(cryptoKey.getEncoded()))
156+
.keyNamespace("Example")
157+
.keyName("RandomKey")
158+
.wrappingAlg(AesWrappingAlg.ALG_AES128_GCM_IV12_TAG16)
159+
.build();
160+
IKeyring keyring = materialProviders.CreateRawAesKeyring(keyringInput);
161+
AwsCrypto crypto = AwsCrypto.standard();
162+
163+
String testDataString = StringUtils.repeat("Hello, World! ", 5_000);
164+
165+
int startOffset = 100; // The data will start from this offset
166+
byte[] inputDataWithOffset = new byte[10_000];
167+
// the length of the actual data
168+
int dataLength = inputDataWithOffset.length - startOffset;
169+
// copy some data, starting at the startOffset
170+
// so the first |startOffset| bytes are 0s
171+
System.arraycopy(
172+
testDataString.getBytes(StandardCharsets.UTF_8),
173+
0,
174+
inputDataWithOffset,
175+
startOffset,
176+
dataLength);
177+
// decryptData (non-streaming) doesn't know about the offset
178+
// it will strip out the original 0s
179+
byte[] expectedOutput = new byte[10_000 - startOffset];
180+
System.arraycopy(
181+
testDataString.getBytes(StandardCharsets.UTF_8), 0, expectedOutput, 0, dataLength);
182+
183+
// Encrypt the data
184+
byte[] encryptedData;
185+
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
186+
try (CryptoOutputStream cryptoOutput =
187+
crypto.createEncryptingStream(keyring, os, Collections.emptyMap())) {
188+
cryptoOutput.write(inputDataWithOffset, startOffset, dataLength);
189+
}
190+
encryptedData = os.toByteArray();
191+
}
192+
193+
// Check non-streaming decrypt
194+
CryptoResult<byte[], ?> nonStreamDecrypt = crypto.decryptData(keyring, encryptedData);
195+
assertEquals(dataLength, nonStreamDecrypt.getResult().length);
196+
assertArrayEquals(expectedOutput, nonStreamDecrypt.getResult());
197+
198+
// Check streaming decrypt
199+
int decryptedLength = 0;
200+
byte[] decryptedData = new byte[inputDataWithOffset.length];
201+
try (ByteArrayInputStream is = new ByteArrayInputStream(encryptedData);
202+
CryptoInputStream cryptoInput = crypto.createDecryptingStream(keyring, is)) {
203+
int offset = startOffset;
204+
do {
205+
int bytesRead = cryptoInput.read(decryptedData, offset, decryptedData.length - offset);
206+
if (bytesRead <= 0) {
207+
break; // End of stream
208+
}
209+
offset += bytesRead;
210+
decryptedLength += bytesRead;
211+
} while (true);
212+
}
213+
assertEquals(dataLength, decryptedLength);
214+
// These arrays will be offset, i.e. the first |startOffset| bytes are 0s
215+
assertArrayEquals(inputDataWithOffset, decryptedData);
216+
}
120217
}

0 commit comments

Comments
 (0)