Use Managed HSM for encryption and decryption
The original entry is in Japanese, published on April 1, 2022.
This article is similar to the following one, but Managed HSM is used instead of Key Vault.
Why managed HSM?
Previously Key Vault (and precisely speaking, Dedicated HSM is another option) is the only option for encryption/decryption but now Managed HSM is also available. Managed HSM allows us to use not only EC and RSA encryption but also AES encryption, that means we can encrypt/decrypt messages faster than using RSA/EC.
In this article, I elaborate how to use Managed HSM to implement the same scenario in the previous article.
Scenario
- Call function (Func-A) to pass messages.
- Func-A uses Managed HSM to encrypt received messages.
- Func-A puts encrypted messages to a topic in Service Bus.
- Func-A returns HTTP 202 to caller.
- Function (Func-B) receives messages from a subscription of topic in Service Bus.
- Func-B uses Managed HSM to decrypt received messages.
- Func-B puts decrypted messages to a queue, which is used to check decrypted messages.
Authentication and access control
System managed identities of Func-A/B are used to configure RBAC for Service Bus and Managed HSM.
Encryption Key type
AES256 is used.
Prerequisites
The following instances are required. Note that Java is used to implement function apps and no VNet is in this article.
Managed HSM
- SKU: B1
- We can create instances with ARM template, Azure CLI, and PowerShell as of April 1, 2022 (Azure Portal is out of scope). Quick start is helpful to start creating instances.
- Please note that Managed HSM instances should be enabled after the instance is created. Please follow the instruction in the quick start.
If authentication/authorization with managed identities against Managed HSM instances, please perform the following steps.
- Ensure that privileges needed to operate Managed HSM are granted to users logging in with
az login
. - If you can confirm privileges are granted, log in again with
az login
explicitly.
Azure Functions
- SKU: Consumption Tier
- OS: Linux
- Runtime: Java 11 (Java 17 is also fine)
Service Bus
- Standard SKU (because topic is used in this scenario)
- Topic: name is
mhsmt1
(subscriptions ares1
, which Func-B subscribes, andm1
used for monitoring purpose). - Queue: name is
mhsmq1
(for decrypted data check purpose).
Please note that Managed HSM instances should be enabled after the instance is created. Please follow the instruction mentioned in the quick start.
RBAC Configuration
1. Managed HSM Management Plane
Azure RBAC is used for access control against Managed HSM management plane. As documentation listed below mentioned, RBAC against both management plane and data plane should be configured explicitly.
Granting a security principal management plane access to a managed HSM does not grant them any access to data plane to access keys or data plane role assignments (Managed HSM local RBAC). This isolation is by design to prevent inadvertent expansion of privileges affecting access to keys stored in Managed HSM.
In this article, my account is configured as a member of Managed HSM Contributor. Also, my account is an administrator of the key, too.
2. Managed HSM Data Plane
To access key(s) for encryption/decryption, system managed identities of each function app are enabled, and RBAC against Managed HSM is configured with the identities.
We can configure RBAC against management plane using Azure RBAC ( az role assignment create...
). In case of RBAC against data plane, however, the only available option is local RBAC (az keyvault role assignment create...
). Assignees and roles are listed below.
- Create and manage keys
- Role: Managed HSM Crypto User
- Assignee: key administrator - Encrypt/Decrypt data with keys
- Role: Managed HSM Crypto User
- Assignee: system managed identities of Func-A/B
For details of roles, please check the following document.
Here is a sample code for role assignment.
assignee_object_id={Object ID. If using Managed Identities, specify Principal ID instead of Object ID.}
hsmName={HSM name}
# Control plane
az role assignment create \
--assignee-object-id $assignee_object_id \
--assignee-principal-type User \
--role "Managed HSM Contributor"
# Data plane: whole Managed HSM is in scope.
# Samples to assign users to "Managed HSM Crypto User"
role=21dbd100-6940-42c2-9190-5d6cb909625b
az keyvault role assignment create \
--hsm-name $hsmName \
--assignee-object-id $assignee_object_id \
--assignee-principal-type User \
--role $role \
--scope /
3. Service Bus
RBAC for Service Bus is the same configuration in the previous article.
Create an AES key
In this article, an AES256 key is created with the following settings.
- Type of keys:
oct-HSM
- Size:
256
Managed HSM can store multiple versions of encryption keys, like Key Vault. For more details, please follow the document below.
$ hsmName={Name of Managed HSM}
$ keyName={Key name}
$ az keyvault key create \
--hsm-name $hsmName \
--kty oct-HSM \
--name $keyName \
--protection hsm \
--size 256
Implementation of Function apps
Dependencies of Maven are listed below (This is as of December 31, 2022).
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-security-keyvault-keys</artifactId>
<version>4.6.0-beta.1</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-messaging-servicebus</artifactId>
<version>7.13.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.7.2</version>
</dependency>
Now create two function apps with the following specs.
- Func-A: A function app which writes messages to a topic
- HTTP Trigger
- After invoked, the function accesses a Managed HSM to encrypt message body.
- Write the encrypted messages to Service Bus topicmhsmt1
. - Func-B: A function app which reads messages from a topic and write them to a queue
- Service Bus Trigger (Topic)
- After invoked, the function accesses the Managed HSM to decrypt messages which are retrieved from Service Bus topicmhsmt1
.
- Write decrypted messages to Service Bus Queuemhsmq1
for message check purpose.
Annotations for Trigger/OutputBinding allows us to configure connection settings to Service Bus.
Implementation is almost the same as one mentioned in the previous article but initialized vector (iv
) is required as AES (CBC mode) is used in this article. So, parameters passed to methods are different from ones in case of RSA/EC. As iv
for AES is 16 bytes long, you can create iv like follows.
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
This is an example code for encryption.
// [Encryption]
// Convert messages to byte arrays
byte[] rawBytes = body.getBytes(StandardCharsets.UTF_8);
// Create parameters with Initialized Vector (iv)
EncryptParameters encryptParameters = EncryptParameters.createA256CbcParameters(rawBytes, iv);
// Pass encryptParameter to methods
EncryptResult encryptResult = cryptoClient.encrypt(encryptParameters, new Context(key, value));
byte[] cipherText = encryptResult.getCipherText();
As the same iv
is required for decryption, iv is put to Service Bus topic along with encrypted messages.
public class Payload {
byte[] cipherText;
byte[] iv;
public byte[] getIv() {
return iv;
}
public void setIv(byte[] iv) {
this.iv = iv;
}
public byte[] getCipherText() {
return cipherText;
}
public void setCipherText(byte[] _cipherText) {
this.cipherText = _cipherText;
}
}
In this article, the following class is created to send messages as well as iv
. Indeed, we should sign messages to improve security, but we omit signing for testing purpose.
Payload payload = new Payload();
payload.setCipherText(encryptResult.getCipherText()); payload.setIv(iv);
Decryption is performed using encrypted messages and iv
received from the subscription s1
of Service Bus topic.
// [Decryption]
// Decrypting byte arrays retrieved from Service Bus.
byte[] cipherText = payload.getCipherText();
// Create parameters with initialized vector
DecryptParameters decryptParameters = DecryptParameters.createA256CbcParameters(cipherText, Payload.iv);
// Pass decryptParameters instead of byte arrays for decryption.
DecryptResult decryptResult = cryptoClient.decrypt( decryptParameters, new Context(key, value));
byte[] rawBytes = decryptResult.getPlainText();
And...
In this article, we used fixed value for Context
for testing purpose, but it is recommended that preferred value or Context.NONE
should be specified.
Codes used in this article are stored in GitHub.