I am trying to decrypt a KMS encrypted AWS SES message stored in S3.
Everything works fine (getting the object and metadata from s3, decrypting the key with KMS) until I try to decrypt the content with the decrypted Key:
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { KMSClient, DecryptCommand } from '@aws-sdk/client-kms';
import { createDecipheriv } from 'crypto';
const s3 = new S3Client( {
// ...credentials
} );
const kms = new KMSClient( {
// ...credentials
} );
const getFromS3AndDecrypt = ( Bucket, Key ) => {
// GetObject command
const command = new GetObjectCommand( {
Bucket,
Key,
} );
// Get the object from S3
const { Metadata, Body } = await s3.send( command );
// Convert to Buffer
const body = await streamToBuffer( Body );
// Get KMS Metadata
var {
[ 'x-amz-key-v2' ]: kmsKeyBase64,
[ 'x-amz-iv' ]: iv,
[ 'x-amz-tag-len' ]: tagLenBits = 0,
[ 'x-amz-cek-alg' ]: algo,
[ 'x-amz-matdesc' ]: matdesc,
} = Metadata;
// Convert tagLenBits to Bytes
const tagLen = tagLenBits / 8;
// Get encryption context
const encryptionContext = JSON.parse( matdesc );
// Get algorithm
switch ( algo ) {
case 'AES/GCM/NoPadding':
algo = `aes-256-gcm`;
break;
case 'AES/CBC/PKCS5Padding':
algo = `aes-256-cbc`;
break;
default:
throw new Error( `Unsupported algorithm ${ algo }` );
}
// Convert kmsKey to Buffer
const kmsKeyBuffer = Buffer.from( kmsKeyBase64, 'base64' );
// DecryptCommand
const kmsCommand = new DecryptCommand( {
CiphertextBlob: kmsKeyBuffer,
EncryptionContext: encryptionContext,
} );
// Decrypt the key with KMS
const { Plaintext } = await kmsClient.send( kmsCommand );
// Create decipher with key and iv
const decipher = createDecipheriv( algo, Buffer.from( Plaintext ), Buffer.from( iv ), {
authTagLength: 16,
} );
// Body without authTag
const data = body.slice( 0, - tagLen );
if ( tagLen !== 0 ) {
// authTag
const tag = body.slice( - tagLen );
// Set authTag
decipher.setAuthTag( tag );
}
// Decrypt data
var decrypted = decipher.update( data, 'binary', 'utf8' );
decrypted += decipher.final( 'utf8' );
return decrypted;
};
// Turn @aws-sdk/client-s3 stream response into buffer @see https://github.com/aws/aws-sdk-js-v3/issues/1877#issuecomment-755387549
const streamToBuffer = ( stream ) => new Promise( ( resolve, reject ) => {
const chunks = [];
stream.on( 'data', ( chunk ) => chunks.push( chunk ) );
stream.on( 'error', reject );
stream.on( 'end', () => resolve( Buffer.concat( chunks ) ) );
} );
When calling decipher.final
I get a Unsupported state or unable to authenticate data
error.
This seems to be either because the authTag
used doesn't match the one which was used when encrypting the data or because the input and output encoding doesn't match.