I have an object that I've successfully encrypted using an Amazon Web Services key (KMS), and then stored it in an S3 bucket. This all works fine from Java, including reading and decrypting, but Node.js does not know how to handle the embedded datakey that AWS creates with the S3 object.
This works (Java):
public class Cryptotest {
final AwsCrypto crypto = new AwsCrypto();
private final Logger logger = Logger.getLogger(getClass().getName());
final KmsMasterKeyProvider prov = new KmsMasterKeyProvider(System.getenv("AesKey"));
public void doWrite() {
AmazonS3 s3Client = AmazonS3ClientBuilder.standard().build();
String contents = "Hello world from KMS-encrypted file!";
final Map<String, String> context = Collections.singletonMap(System.getenv("KeyContext"),
System.getenv("KeyValue"));
final byte[] ciphertext = crypto.encryptData(prov, contents.getBytes(), context).getResult();
ObjectMetadata metadata = new ObjectMetadata();
metadata.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION);
metadata.setContentLength(ciphertext.length);
if (!s3Client.doesObjectExist(System.getenv("Bucket"), System.getenv("Path"))) {
PutObjectRequest writeMe = new PutObjectRequest(System.getenv("Bucket"), System.getenv("Path"),
new ByteArrayInputStream(ciphertext), metadata);
s3Client.putObject(writeMe);
} else {
logger.debug("Object already exists, will not overwrite!");
}
}
public void doWork() throws IOException {
AmazonS3 s3Client = AmazonS3ClientBuilder.standard().build();
S3Object xFile = s3Client.getObject(System.getenv("Bucket"), System.getenv("Path"));
logger.debug("Got S3 object");
InputStream fileContents = xFile.getObjectContent();
byte[] read_buf = new byte[1024];
ByteArrayOutputStream os = new ByteArrayOutputStream();
int read_len = 0;
while ((read_len = fileContents.read(read_buf)) > 0) {
os.write(read_buf, 0, read_len);
}
fileContents.close();
logger.debug("Read object contents");
logger.debug(os.toString(StandardCharsets.UTF_8.name()));
CryptoResult<byte[], KmsMasterKey> decryptResult = crypto.decryptData(prov, os.toByteArray());
logger.debug("Decrypted object");
if (!decryptResult.getMasterKeyIds().get(0).equals(System.getenv("AesKey"))) {
throw new IllegalStateException("Wrong key ID!");
}
logger.debug("Verified object contents");
final Map<String, String> context = Collections.singletonMap(System.getenv("KeyContext"),
System.getenv("KeyValue"));
logger.debug("Created encryption context map");
// Also, verify that the encryption context in the result contains the
// encryption context supplied to the encryptString method. Because the
// SDK can add values to the encryption context, don't require that
// the entire context matches.
for (final Map.Entry<String, String> e : context.entrySet()) {
if (!e.getValue().equals(decryptResult.getEncryptionContext().get(e.getKey()))) {
throw new IllegalStateException("Wrong Encryption Context!");
}
}
logger.debug("Finished decrypting object");
logger.debug(new String(decryptResult.getResult()));
}
}
This throws an InvalidCiphertextException (Node.js) because of (I think) the embedded Datakey. I have tried different permutations of toString, Buffer, and decoding type (binary, ascii, etc.) with the same error:
return s3.getObject(keyParams).promise()
.then(encryptedAesKey => {
var cipherParams = {
CiphertextBlob: new Buffer(encryptedAesKey.Body, 'base64'),
EncryptionContext: {
"KeyContext": "KeyValue"
}
};
return kms.decrypt(cipherParams).promise();
})
I'm pretty sure it's the embedded datakey that's causing the problem because when I send the contents of the S3 object to the console, I can see in plaintext the name of the datakey (among a bunch of unicode representations of the binary data), like this:
arn:aws:kms:us-east-1:XXXXXXXXX:key/YYYYY-YYYYYY-YYYYYYYY
I do not see this in the S3 files encrypted from Node.js. The Java documentation refers to the unwrapping of the datakey:
AwsCrypto.decryptData(MasterKeyProvider<K> provider, byte[] ciphertext)
Decrypts the provided ciphertext by requesting that the provider unwrap any usable DataKey in the ciphertext and then decrypts the ciphertext using that DataKey.
How do I unwrap the datakey from the S3 object using Node.js?