31
31
import java .util .HashMap ;
32
32
import java .util .Map ;
33
33
import java .util .Set ;
34
+ import java .util .concurrent .ConcurrentHashMap ;
35
+ import java .util .function .Function ;
34
36
35
37
import javax .crypto .Cipher ;
36
38
import javax .crypto .SecretKey ;
@@ -58,7 +60,16 @@ public class DynamoDBEncryptor {
58
60
private static final String DEFAULT_DESCRIPTION_BASE = "amzn-ddb-map-" ; // Same as the Mapper
59
61
private static final Charset UTF8 = Charset .forName ("UTF-8" );
60
62
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
+
62
73
private static final int CURRENT_VERSION = 0 ;
63
74
64
75
private String signatureFieldName = DEFAULT_SIGNATURE_FIELD ;
@@ -339,7 +350,7 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
339
350
final String encryptionMode = encryptionKey != null ? encryptionKey .getAlgorithm () +
340
351
materialDescription .get (symmetricEncryptionModeHeader ) : null ;
341
352
Cipher cipher = null ;
342
- int ivSize = -1 ;
353
+ int blockSize = -1 ;
343
354
344
355
for (Map .Entry <String , AttributeValue > entry : itemAttributes .entrySet ()) {
345
356
Set <EncryptionFlags > flags = attributeFlags .get (entry .getKey ());
@@ -354,15 +365,13 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
354
365
plainText = ByteBuffer .wrap (((DelegatedKey )encryptionKey ).decrypt (toByteArray (cipherText ), null , encryptionMode ));
355
366
} else {
356
367
if (cipher == null ) {
357
- cipher = Cipher .getInstance (
358
- encryptionMode );
359
- ivSize = cipher .getBlockSize ();
368
+ blockSize = getBlockSize (encryptionMode );
369
+ cipher = Cipher .getInstance (encryptionMode );
360
370
}
361
- byte [] iv = new byte [ivSize ];
371
+ byte [] iv = new byte [blockSize ];
362
372
cipherText .get (iv );
363
373
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 ()));
366
375
cipher .doFinal (cipherText , plainText );
367
376
plainText .rewind ();
368
377
}
@@ -371,6 +380,10 @@ private void actualDecryption(Map<String, AttributeValue> itemAttributes,
371
380
}
372
381
}
373
382
383
+ protected int getBlockSize (final String encryptionMode ) {
384
+ return BLOCK_SIZE_CACHE .computeIfAbsent (encryptionMode , BLOCK_SIZE_CALCULATOR );
385
+ }
386
+
374
387
/**
375
388
* This method has the side effect of replacing the plaintext
376
389
* attribute-values of "itemAttributes" with ciphertext attribute-values
@@ -388,7 +401,7 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
388
401
encryptionMode = encryptionKey .getAlgorithm () + SYMMETRIC_ENCRYPTION_MODE ;
389
402
}
390
403
Cipher cipher = null ;
391
- int ivSize = -1 ;
404
+ int blockSize = -1 ;
392
405
393
406
for (Map .Entry <String , AttributeValue > entry : itemAttributes .entrySet ()) {
394
407
Set <EncryptionFlags > flags = attributeFlags .get (entry .getKey ());
@@ -405,16 +418,22 @@ private void actualEncryption(Map<String, AttributeValue> itemAttributes,
405
418
dk .encrypt (toByteArray (plainText ), null , encryptionMode ));
406
419
} else {
407
420
if (cipher == null ) {
421
+ blockSize = getBlockSize (encryptionMode );
408
422
cipher = Cipher .getInstance (encryptionMode );
409
- ivSize = cipher .getBlockSize ();
410
423
}
411
424
// Encryption format: <iv><ciphertext>
412
425
// 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 );
417
429
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 );
418
437
cipherText .rewind ();
419
438
}
420
439
// Replace the plaintext attribute value with the encrypted content
@@ -539,17 +558,22 @@ protected static Map<String, String> unmarshallDescription(AttributeValue attrib
539
558
attributeValue .getB ().reset ();
540
559
}
541
560
}
542
-
561
+
543
562
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 ) {
545
569
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
+ }
553
573
}
574
+
575
+ byte [] result = new byte [buffer .remaining ()];
576
+ buffer .get (result );
577
+ return result ;
554
578
}
555
579
}
0 commit comments