3131import java .util .HashMap ;
3232import java .util .Map ;
3333import java .util .Set ;
34+ import java .util .concurrent .ConcurrentHashMap ;
35+ import java .util .function .Function ;
3436
3537import javax .crypto .Cipher ;
3638import javax .crypto .SecretKey ;
@@ -58,7 +60,16 @@ public class DynamoDBEncryptor {
5860 private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-" ; // Same as the Mapper
5961 private static final Charset UTF8 = Charset .forName ("UTF-8" );
6062 private static final String SYMMETRIC_ENCRYPTION_MODE = "/CBC/PKCS5Padding" ;
61-
63+ private static final ConcurrentHashMap <String , Integer > BLOCK_SIZE_CACHE = new ConcurrentHashMap <>();
64+ private static final Function <String , Integer > BLOCK_SIZE_CALCULATOR = (transformation ) -> {
65+ try {
66+ final Cipher c = Cipher .getInstance (transformation );
67+ return c .getBlockSize ();
68+ } catch (final GeneralSecurityException ex ) {
69+ throw new IllegalArgumentException ("Algorithm does not exist" , ex );
70+ }
71+ };
72+
6273 private static final int CURRENT_VERSION = 0 ;
6374
6475 private String signatureFieldName = DEFAULT_SIGNATURE_FIELD ;
@@ -339,7 +350,7 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
339350 final String encryptionMode = encryptionKey != null ? encryptionKey .getAlgorithm () +
340351 materialDescription .get (symmetricEncryptionModeHeader ) : null ;
341352 Cipher cipher = null ;
342- int ivSize = -1 ;
353+ int blockSize = -1 ;
343354
344355 for (Map .Entry <String , AttributeValue > entry : itemAttributes .entrySet ()) {
345356 Set <EncryptionFlags > flags = attributeFlags .get (entry .getKey ());
@@ -354,15 +365,13 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
354365 plainText = ByteBuffer .wrap (((DelegatedKey )encryptionKey ).decrypt (toByteArray (cipherText ), null , encryptionMode ));
355366 } else {
356367 if (cipher == null ) {
357- cipher = Cipher .getInstance (
358- encryptionMode );
359- ivSize = cipher .getBlockSize ();
368+ blockSize = getBlockSize (encryptionMode );
369+ cipher = Cipher .getInstance (encryptionMode );
360370 }
361- byte [] iv = new byte [ivSize ];
371+ byte [] iv = new byte [blockSize ];
362372 cipherText .get (iv );
363373 cipher .init (Cipher .DECRYPT_MODE , encryptionKey , new IvParameterSpec (iv ), Utils .getRng ());
364- plainText = ByteBuffer .allocate (
365- cipher .getOutputSize (cipherText .remaining ()));
374+ plainText = ByteBuffer .allocate (cipher .getOutputSize (cipherText .remaining ()));
366375 cipher .doFinal (cipherText , plainText );
367376 plainText .rewind ();
368377 }
@@ -371,6 +380,10 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
371380 }
372381 }
373382
383+ protected int getBlockSize (final String encryptionMode ) {
384+ return BLOCK_SIZE_CACHE .computeIfAbsent (encryptionMode , BLOCK_SIZE_CALCULATOR );
385+ }
386+
374387 /**
375388 * This method has the side effect of replacing the plaintext
376389 * attribute-values of "itemAttributes" with ciphertext attribute-values
@@ -388,7 +401,7 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
388401 encryptionMode = encryptionKey .getAlgorithm () + SYMMETRIC_ENCRYPTION_MODE ;
389402 }
390403 Cipher cipher = null ;
391- int ivSize = -1 ;
404+ int blockSize = -1 ;
392405
393406 for (Map .Entry <String , AttributeValue > entry : itemAttributes .entrySet ()) {
394407 Set <EncryptionFlags > flags = attributeFlags .get (entry .getKey ());
@@ -405,16 +418,22 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
405418 dk .encrypt (toByteArray (plainText ), null , encryptionMode ));
406419 } else {
407420 if (cipher == null ) {
421+ blockSize = getBlockSize (encryptionMode );
408422 cipher = Cipher .getInstance (encryptionMode );
409- ivSize = cipher .getBlockSize ();
410423 }
411424 // Encryption format: <iv><ciphertext>
412425 // Note a unique iv is generated per attribute
413- byte [] iv = Utils .getRandom (ivSize );
414- cipher .init (Cipher .ENCRYPT_MODE , encryptionKey , new IvParameterSpec (iv ), Utils .getRng ());
415- cipherText = ByteBuffer .allocate (ivSize + cipher .getOutputSize (plainText .remaining ()));
416- cipherText .put (iv );
426+ cipher .init (Cipher .ENCRYPT_MODE , encryptionKey , Utils .getRng ());
427+ cipherText = ByteBuffer .allocate (blockSize + cipher .getOutputSize (plainText .remaining ()));
428+ cipherText .position (blockSize );
417429 cipher .doFinal (plainText , cipherText );
430+ cipherText .flip ();
431+ final byte [] iv = cipher .getIV ();
432+ if (iv .length != blockSize ) {
433+ throw new IllegalStateException (String .format ("Generated IV length (%d) not equal to block size (%d)" ,
434+ iv .length , blockSize ));
435+ }
436+ cipherText .put (iv );
418437 cipherText .rewind ();
419438 }
420439 // Replace the plaintext attribute value with the encrypted content
@@ -539,17 +558,22 @@ protected static Map<String, String> unmarshallDescription(AttributeValue attrib
539558 attributeValue .getB ().reset ();
540559 }
541560 }
542-
561+
543562 private static byte [] toByteArray (ByteBuffer buffer ) {
544- if (buffer .hasArray ()) {
563+ buffer = buffer .duplicate ();
564+ // We can only return the array directly if:
565+ // 1. The ByteBuffer exposes an array
566+ // 2. The ByteBuffer starts at the beginning of the array
567+ // 3. The ByteBuffer uses the entire array
568+ if (buffer .hasArray () && buffer .arrayOffset () == 0 ) {
545569 byte [] result = buffer .array ();
546- buffer .rewind ();
547- return result ;
548- } else {
549- byte [] result = new byte [buffer .remaining ()];
550- buffer .get (result );
551- buffer .rewind ();
552- return result ;
570+ if (buffer .remaining () == result .length ) {
571+ return result ;
572+ }
553573 }
574+
575+ byte [] result = new byte [buffer .remaining ()];
576+ buffer .get (result );
577+ return result ;
554578 }
555579}
0 commit comments