Showing posts with label AWS KMS. Show all posts
Showing posts with label AWS KMS. Show all posts

AWS: Envelop Encryption & Decryption using AWS Key Management Service (Envelop encryption) - Node.JS Version

Recently I published a post on AWS KMS encryption using .net / .net core. Now I got a chance to work on AWS KMS encryption using Node.Js. Following is the Node.Js version of the KMS helper class given in the previous post.
I used the aws-sdk npm for this

encrypt.js

'use strict';
var AWS = require("aws-sdk"),
    crypto = require('crypto'),
    algorithm = 'AES-256-CBC';
var awsconfig = {
    "accessKey": "<Your AWS Access Key>",
    "secretAccessKey": "<Your AWS Secret Key>",
    "region": "<Your AWS region>",
    "cmkArn": "<Your KMS master Key arn >" // The identifier of the CMK to use to encrypt the data key. You can use the key ID or Amazon Resource Name (ARN) of the CMK, or the name or ARN of an alias that refers to the CMK.
}

// Creates the KMS client
function getKMSClient() {
    var credentials = new AWS.Credentials(awsconfig.accessKey, awsconfig.secretAccessKey);
    AWS.config.update({
        region: awsconfig.region,
        credentials: credentials
    });

    return new AWS.KMS();
}

// Generates the KMS data Key
function generateDataKey() {
    const kms = getKMSClient();
    return new Promise((resolve, reject) => {
        const params = {
            KeyId: awsconfig.cmkArn,
            KeySpec: 'AES_256'// Specifies the type of data key to return.
        };
        kms.generateDataKey(params, (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

// Decrypt the KMS Data key
function decryptDataKey(CiphertextBlob) {
    const kms = getKMSClient();
    return new Promise((resolve, reject) => {
        kms.decrypt({ CiphertextBlob }, (err, data) => {
            if (err) {
                reject(err);
            } else {
                resolve(data);
            }
        });
    });
}

module.exports = {

    encrypt: function (text) {
        return new Promise((resolve, reject) => {
            generateDataKey().then(dataKey => {
                // Get a random IV
                const iv = crypto.randomBytes(16);
                // Copy the length of the CiphertextBlob as first byte and concatenate the CiphertextBlob to it
                var length = Buffer.from(['0x' + dataKey.CiphertextBlob.length.toString(16)]);
                var str = Buffer.concat([length, dataKey.CiphertextBlob])
                // Concatenate the iv to the buffer
                str = Buffer.concat([str, iv]);
                
                // Create aes encryptor with KMS plain key
                var encryptor = crypto.createCipheriv(algorithm, dataKey.Plaintext, iv);
                // Encrypt the data
                encryptor.write(text);
                encryptor.end();
                // Concatenate the encrypted to the buffer and return the base64 string
                str = Buffer.concat([str, encryptor.read()]);
                resolve(str.toString('base64'));
            }, error => {
                reject(error);
            });
        });

    },

    decrypt: function (text) {
        return new Promise((resolve, reject) => {
            try {
                // Convert the base64 string to buffer
                var buf = Buffer.from(text, 'base64');
                // Get the CiphertextBlob length stored in the firsty byte
                var length = parseInt(buf[0].toString());
                // Extract the CiphertextBlob, IV and 
                var cipherBlob = buf.slice(1, length+1);
                var iv = buf.slice(length + 1, length + 1 + 16);
                var encText = buf.slice(length + 1 + 16, buf.length);
                // Decrypt the CiphertextBlob and get the plaintext key
                decryptDataKey(cipherBlob).then(dataKey => {
                    // Create aes decryptor and decrypt the text
                    var decryptor = crypto.createDecipheriv(algorithm, dataKey.Plaintext, iv)
                    decryptor.write(encText);
                    decryptor.end();
                    resolve(decryptor.read().toString());
                }, error => {
                    reject(error);
                });
            }
            catch (err) {
                reject(error);
            }
        });
    }

}

routes.js

'use strict';
var express = require('express');
var kmsHelper = require('./encrypt');
var router = express.Router();

/* GET home page. */
router.get('/', function (req, res) {
    res.render('home.html');
});

router.post('/api/encrypt', function (req, res) {
    try {
        kmsHelper.encrypt(req.body.text).then(result => {
            res.send(result.toString());
        }, err => {
            res.statusCode = 404;
            res.send(err);
        });
    }
    catch (ex) {
        res.statusCode = 404;
        res.send(ex);
    }
});

router.post('/api/decrypt', function (req, res) {
    try {
        kmsHelper.decrypt(req.body.text).then(result => {
            res.send(result.toString());
        }, err => {
            res.statusCode = 404;
            res.send(err);
        });
    }
    catch (ex) {
        res.statusCode = 404;
        res.send(ex);
    }
});


module.exports = router;    

Happy Coding! 😊

AWS: Envelop Encryption & Decryption using AWS Key Management Service (Envelop encryption)

As per Amazon, AWS Key Management Service (AWS KMS) is a managed service that makes it easy for you to create and control the encryption keys used to encrypt your data. The master keys that you create in AWS KMS are protected by FIPS 140-2 validated cryptographic modules.

The primary resources in AWS KMS are customer master keys (CMKs). You can use a CMK to encrypt and decrypt up to 4 KB (4096 bytes) of data. Typically, you use CMKs to generate, encrypt, and decrypt the data keys that you use outside of AWS KMS to encrypt your data. This strategy is known as envelope encryption.

Encryption process

  • Generate a data key using the GenerateDataKey operation of AWS KMS. The operation returns a plaintext copy of the data key and a copy of the data key encrypted using the master key (CMK)
  • Encrypt the data with the plain data key and destroy the key
  • We can store this encrypted key along with our encrypted message.The message can’t be decrypted unless you can decrypt the data key and the data key can’t be decrypted unless you have access to the master key.

Decryption process

  • Decrypt the encrypted key using our master key to get the plain data key
  • Decrypt the data with the plain data key
You can read the KMS concepts here.

Following is the helper class for Encrypt or Decrypt the data with KMS key in .NET/.NET Core with C#. AWSSDK.KeyManagementService  is the nuget package required to work with AWS KMS.

using Amazon.KeyManagementService;
using Amazon.KeyManagementService.Model;
using Amazon.Runtime;
using log4net;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;


public class KMSHelper
{
    private AmazonKeyManagementServiceClient client;
    private static readonly ILog _logger = LogManager.GetLogger(typeof(KMSHelper));
    private readonly string accessKey, secretKey, serviceUrl;

    public KMSHelper(string accessKey, string secretKey, string serviceUrl)
    {
        this.accessKey = accessKey;
        this.secretKey = secretKey;
        this.serviceUrl = serviceUrl;
        client = GetClient();
    }

    private AmazonKeyManagementServiceClient GetClient()
    {
        if (client == null)
        {
            try
            {
                // DynamoDB config object
                AmazonKeyManagementServiceConfig clientConfig = new AmazonKeyManagementServiceConfig
                {
                    // Set the endpoint URL
                    ServiceURL = serviceUrl
                };
                client = new AmazonKeyManagementServiceClient(accessKey, secretKey, clientConfig);
            }
            catch (AmazonKeyManagementServiceException ex)
            { _logger.Error($"Error (AmazonKeyManagementServiceException) creating KMS client", ex); }
            catch (AmazonServiceException ex)
            { _logger.Error($"Error (AmazonServiceException) creating KMS client", ex); }
            catch (Exception ex)
            { _logger.Error($"Error creating KMS client", ex); }
        }
        return client;
    }

    /// <summary>
    /// Generates new data key under the master key
    /// </summary>
    /// <param name="masterKeyId">Master key</param>
    /// <returns></returns>
    private async Task<MemoryStream> GenerateDataKey(string masterKeyId)
    {
        using (var kmsClient = GetClient())
        {
            var result = await kmsClient.GenerateDataKeyAsync(new GenerateDataKeyRequest
            {
                KeyId = masterKeyId,
                KeySpec = DataKeySpec.AES_256
            });

            return result.CiphertextBlob;
        }
    }

    /// <summary>
    /// Decrypts the data key using the master key
    /// </summary>
    /// <param name="ciphertext">Stream to decrypt</param>
    /// <returns></returns>
    private async Task<MemoryStream> DecryptDataKey(MemoryStream ciphertext)
    {
        using (var kmsClient = GetClient())
        {
            var decryptionResponse = await kmsClient.DecryptAsync(new DecryptRequest
            {
                CiphertextBlob = ciphertext
            });

            return decryptionResponse.Plaintext;
        }
    }

    /// <summary>
    /// Encrypts the data key using the master key
    /// </summary>
    /// <param name="masterKeyId">Master key arn</param>
    /// <param name="plaintext">Plain data key</param>
    /// <returns></returns>
    private async Task<string> EncryptDataKey(string masterKeyId, MemoryStream plaintext)
    {
        using (var kmsClient = new AmazonKeyManagementServiceClient(accessKey, secretKey, serviceUrl))
        {
            var encryptionResponse = await kmsClient.EncryptAsync(new EncryptRequest
            {
                KeyId = masterKeyId,
                Plaintext = plaintext
            });

            return Convert.ToBase64String(encryptionResponse.CiphertextBlob.ToArray());
        }
    }

    /// <summary>
    /// Encrypts the text using the master key
    /// </summary>
    /// <param name="textToEncrypt">Text to encrypt</param>
    /// <param name="masterKeyId">Master key arn</param>
    /// <returns></returns>
    public async Task<string> Encrypt(byte[] textToEncrypt, string masterKeyId)
    {
        var kmsClient = new AmazonKeyManagementServiceClient(accessKey, secretKey, serviceUrl);

        using (var algorithm = Aes.Create())
        {
            // Create the streams used for encryption.
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                // Generates the data key under the master key
                var dataKey = await kmsClient.GenerateDataKeyAsync(new GenerateDataKeyRequest
                {
                    KeyId = masterKeyId,
                    KeySpec = DataKeySpec.AES_256
                });

                msEncrypt.WriteByte((byte)dataKey.CiphertextBlob.Length);
                dataKey.CiphertextBlob.CopyTo(msEncrypt);
                algorithm.Key = dataKey.Plaintext.ToArray();

                // Writing algorithm.IV in output stream for decryption purpose.
                msEncrypt.Write(algorithm.IV, 0, algorithm.IV.Length);

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = algorithm.CreateEncryptor(algorithm.Key, algorithm.IV);

                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (MemoryStream input = new MemoryStream(textToEncrypt))
                    {
                        input.CopyTo(csEncrypt);
                        csEncrypt.FlushFinalBlock();
                    }
                    return Convert.ToBase64String(msEncrypt.ToArray());
                }
            }
        }
    }

    /// <summary>
    /// Decrypts the text encrypted with the master key
    /// </summary>
    /// <param name="textToDecrypt">Encrypted text to decrypt</param>
    /// <returns></returns>
    public async Task<string> Decrypt(byte[] textToDecrypt)
    {
        // Create the streams used for decryption.
        using (MemoryStream msDecrypt = new MemoryStream(textToDecrypt))
        {
            var length = msDecrypt.ReadByte();
            var buffer = new byte[length];
            msDecrypt.Read(buffer, 0, length);

            // Decrypt the datakey
            MemoryStream dataKeyCipher = await DecryptDataKey(new MemoryStream(buffer));

            using (var algorithm = Aes.Create())
            {
                algorithm.Key = dataKeyCipher.ToArray();

                var iv = algorithm.IV;
                msDecrypt.Read(iv, 0, iv.Length);
                algorithm.IV = iv;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = algorithm.CreateDecryptor(algorithm.Key, algorithm.IV);

                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (MemoryStream srDecrypt = new MemoryStream())
                    {
                        csDecrypt.CopyTo(srDecrypt);

                        //Write all data to the stream.
                        return Encoding.ASCII.GetString(srDecrypt.ToArray());
                    }
                }
            }
        }
    }
}
Happy Coding!😊