Here's a quick code snippet on how to implement field level encryption of data stored in DynamoDB using per-record encryption keys and the AWS Key management store (KMS).

Before starting you'll want to create a KMS key that will just be used for this service and take note of the Key ID.

Also, I recommend enabling at-rest encryption of the DynamoDB table.

This example assumes a table structured similar to this:

1
2
3
4
5
6
{
  id: 'foobar',
  nonSensitiveStuff: 'will not be encrypted',
  sensitiveStuff: 'will be encrypted',
  otherStuff: 'will not be encrypted'
}

the id field is the primary key. As an enhancement, if your primary key is also sensitive you could instead use a hash of the ID instead of the actual value (although you'd lose the ability to search through the IDs).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
const AWS = require('aws-sdk'); 
const crypto = require('crypto');

const ALGO = 'aes256';

class EnvelopeEncrypt {

  // Asuming input is object, and field we want to encrypt is sensitiveStuff
  // Also assuming that there is an 'id' field which is the primary key.
  // And also this assume UTF8 content...
  saveData(userInfo) { 
    const kms = new AWS.KMS();
    return kms.generateDataKey({ KeyId: 'YOUR_KMS_ENCRYPTION_KEY_ID', KeySpec: 'AES_256' }).promise()
      .then((dataKey) => {
        Object.assign(userInfo,{
          dataKey: dataKey.CiphertextBlob,
          sensitiveStuff: this.encrypt(dataKey.Plaintext, userInfo.sensitiveStuff)
        });
        const params = {
          TableName: 'YOUR_DYNAMO_DB_TABLE_NAME',
          Item: userInfo,
        };
        const dynamodb = new AWS.DynamoDB.DocumentClient();
        return dynamodb.put(params).promise();
      });
  }

  loadData(id) {
    const dynamodb = new AWS.DynamoDB.DocumentClient();
    const params = {
      TableName: 'YOUR_DYNAMO_DB_TABLE_NAME',
      Key: { id },
    };
    return dynamodb.get(params).promise()
      .then((record) => {
        if (record.Item !== undefined) {
          const kms = new AWS.KMS();
          return kms.decrypt({ CiphertextBlob: Buffer.from(record.Item.dataKey, 'base64') }).promise()
            .then((dataKey) => {
              Object.assign(record.Item, { sensitiveStuff: this.decrypt(dataKey.Plaintext.toString('base64'), record.Item.sensitiveStuff) });
              return record.Item;
            });
        }
        return undefined;
      });
  }

  encrypt(keyBase64, payload) {
    const cipher = crypto.createCipher(ALGO, Buffer.from(keyBase64, 'base64'));
    return (Buffer.concat([cipher.update(payload, 'utf8'), cipher.final()])).toString('base64');
  }

  decrypt(keyBase64, payload) {
    const cipher = crypto.createDecipher(ALGO, Buffer.from(keyBase64, 'base64'));
    return (Buffer.concat([cipher.update(payload, 'base64'), cipher.final()])).toString('utf8');
  }
}

module.exports = EnvelopeEncrypt;