node.jsencryptionaws-lambdaamazon-kms

Decrypt text with AWS KMS in NodeJs


I am trying to decrypt some text encrypted with AWS KMS using aws-sdk and NodeJs. I started to play today with NodeJs so I am a newbie with it. I have this problem resolved with Java but I am trying to migrate an existing Alexa skill from Java to NodeJs.

The code to decrypt is:

function decrypt(buffer) {
    const kms = new aws.KMS({
        accessKeyId: 'accessKeyId',
        secretAccessKey: 'secretAccessKey',
        region: 'eu-west-1'
    });
    return new Promise((resolve, reject) => {
        let params = {
            "CiphertextBlob" : buffer,
        };
        kms.decrypt(params, (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data.Plaintext);
            }
        });
    });
};

When I run this code with a correct CiphertextBlob, I get this error:

Promise {
  <rejected> { MissingRequiredParameter: Missing required key 'CiphertextBlob' in params
    at ParamValidator.fail (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:50:37)
    at ParamValidator.validateStructure (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:61:14)
    at ParamValidator.validateMember (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:88:21)
    at ParamValidator.validate (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\param_validator.js:34:10)
    at Request.VALIDATE_PARAMETERS (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\event_listeners.js:126:42)
    at Request.callListeners (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\sequential_executor.js:106:20)
    at callNextListener (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\sequential_executor.js:96:12)
    at D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\event_listeners.js:86:9
    at finish (D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\config.js:349:7)
    at D:\Developing\abono-transportes-js\node_modules\aws-sdk\lib\config.js:367:9
  message: 'Missing required key \'CiphertextBlob\' in params',
  code: 'MissingRequiredParameter',
  time: 2019-06-30T20:29:18.890Z } }

I don't understand why I am receiving that if CiphertextBlob is in the params variable.

Anyone knows? Thanks in advance!

EDIT 01/07

Test to code the feature: First function:

const CheckExpirationDateHandler = {
    canHandle(handlerInput) {
        return handlerInput.requestEnvelope.request.type === 'IntentRequest'
            && handlerInput.requestEnvelope.request.intent.name === 'TtpConsultaIntent';
    },
    handle(handlerInput) {

        var fecha = "";
        var speech = "";

        userData = handlerInput.attributesManager.getSessionAttributes();

        if (Object.keys(userData).length === 0) {
            speech = consts.No_Card_Registered;
        } else {
            console.log("Retrieving expiration date from 3rd API");
            fecha = crtm.expirationDate(cipher.decrypt(userData.code.toString()));
            speech = "Tu abono caducará el " + fecha;
        }

        return handlerInput.responseBuilder
            .speak(speech)
            .shouldEndSession(true)
            .getResponse();

    }
}

Decrypt function provided with a log:

// source is plaintext
async function decrypt(source) {

    console.log("Decrypt func INPUT: " + source)
    const params = {
        CiphertextBlob: Buffer.from(source, 'base64'),
    };
    const { Plaintext } = await kms.decrypt(params).promise();
    return Plaintext.toString();
};

Output:

2019-07-01T19:01:12.814Z 38b45272-809d-4c84-b155-928bee61a4f8 INFO Retrieving expiration date from 3rd API 2019-07-01T19:01:12.814Z 38b45272-809d-4c84-b155-928bee61a4f8 INFO Decrypt func INPUT: AYADeHK9xoVE19u/3vBTiug3LuYAewACABVhd3MtY3J5cHRvLXB1YmxpYy1rZXkAREF4UW0rcW5PSElnY1ZnZ2l1bHQ2bzc3ZnFLZWZMM2J6YWJEdnFCNVNGNzEyZGVQZ1dXTDB3RkxsdDJ2dFlRaEY4UT09AA10dHBDYXJkTnVtYmVyAAt0aXRsZU51bWJlcgABAAdhd3Mta21zAEthcm46YXdzOmttczpldS13ZXN0LTE6MjQwMTE3MzU1MTg4OmtleS81YTRkNmFmZS03MzkxLTRkMDQtYmUwYi0zZDJlMWRhZTRkMmIAuAECAQB4sE8Iv75TZ0A9b/ila9Yi/3vTSja3wM7mN/B0ThqiHZEBxYsoWpX7jCqHMoeoYOkVtAAAAH4wfAYJKoZIhvcNAQcGoG8wbQIBADBoBgkqhkiG9w0BBwEwHgYJYIZIAWUDBAEuMBEEDNnGIwghz+b42E07KAIBEIA76sV3Gmp5ib99S9H4MnY0d1l............ 2019-07-01T19:01:12.925Z 38b45272-809d-4c84-b155-928bee61a4f8 INFO Error handled: handlerInput.responseBuilder.speak(...).shouldEndSession is not a function 2019-07-01T19:01:13.018Z 38b45272-809d-4c84-b155-928bee61a4f8 ERROR Unhandled Promise Rejection {"errorType":"Runtime.UnhandledPromiseRejection","errorMessage":"InvalidCiphertextException: null","stack":["Runtime.UnhandledPromiseRejection: InvalidCiphertextException: null","...


Solution

  • That either means you're missing key 'CiphertextBlob' or its value is undefined.

    Please checkout the value you're passing in as buffer.

    For reference, I also added my working code example that I used.

    import { KMS } from 'aws-sdk';
    
    import config from '../config';
    
    const kms = new KMS({
      accessKeyId: config.aws.accessKeyId,
      secretAccessKey: config.aws.secretAccessKey,
      region: config.aws.region,
    });
    
    // source is plaintext
    async function encrypt(source) {
      const params = {
        KeyId: config.aws.kmsKeyId,
        Plaintext: source,
      };
      const { CiphertextBlob } = await kms.encrypt(params).promise();
    
      // store encrypted data as base64 encoded string
      return CiphertextBlob.toString('base64');
    }
    
    // source is plaintext
    async function decrypt(source) {
      const params = {
        CiphertextBlob: Buffer.from(source, 'base64'),
      };
      const { Plaintext } = await kms.decrypt(params).promise();
      return Plaintext.toString();
    }
    
    export default {
      encrypt,
      decrypt,
    };
    

    ----- ADDED -----

    I was able to reproduce your issue.

    decrypt("this text has never been encrypted before!");
    

    This code throws same error.

    So if you pass plain text that has never been encrypted before or has been encrypted with different key, it throws InvalidCiphertextException: null.

    Now I'll give you one usage example.

    encrypt("hello world!") // this will return base64 encoded string
      .then(decrypt) // this one accepts encrypted string
      .then(decoded => console.log(decoded)); // hello world!