Think of Client-Side Encryption (CSE) as a strategy that has proven to be most effective in augmenting data security and modern precursor to traditional approaches. In addition to providing a stronger security posture, this strategy is also in compliance with major data regulations like GDPR, FERPA and PCI-DSS. In this article, we’ll explore how CSE can provide superior protection for your data, particularly if an authentication and authorization account is compromised. We’ll also address common questions about Microsoft's stance on CSE and explain why CSE might not be as widely discussed as Client-Side Key Encryption (CSKE). By understanding these concepts, you can better meet security and regulatory requirements and ensure that your data remains protected.
As shown below in the Figure 1: An example of Use Case Architecture. Protecting and managing financial and health sensitive data for clients is subject to stringent regulatory requirements such as GDPR and PCI-DSS and also the followings:
As it shown in the Figure 2: The Concept, the processes involved in client-side encryption (CSE) are the followings:
Client-Side Encryption (CSE) provides a significant security advantage by allowing organizations to maintain complete control over their data and encryption keys. This method not only enhances data security but also supports compliance with regulatory requirements, offering peace of mind in the ever-evolving landscape of cloud computing. CSE encrypts data before it is sent to any service like Azure and this means that the data is encrypted on the client’s side, and Azure never sees the encryption keys. Even if someone gains access to your Azure account, they cannot read your data without the keys. In contrast, Client-Side Key Encryption (CSKE) focuses on securing the encryption keys themselves. The client manages and controls these keys, ensuring they are not accessible to the cloud service. This adds an extra layer of protection by keeping the keys out of the service provider’s reach. Both approaches enhance security but address different aspects of data protection.
Most of Azure’s documentation and services tend to emphasize Server-Side Encryption (SSE) due to its simplicity and ease of use for most scenarios. SSE allows Azure to provide a straightforward security model that works out-of-the-box for users, without requiring significant changes to their applications or processes.
However, Azure does support CSE through various SDKs and client libraries. For instance, Azure Storage SDKs for different programming languages provide support for encrypting data on the client side before uploading it to Azure Blob Storage.
You will set up Azure Key Vault to store and manage encryption keys securely.
You will Navigate to Administrators by click on it, then Click Add principal, then select your apps as seen on the screen below:
This will Set the permissions for the Key Vault, ensuring that your application can access the keys. So, review to create and Click on Create button as shown below:
It might take a couple of minutes for your Azure Key Vault creation to deploy successfully.
Next is to create a new key or import an existing key in the Key Vault. This key will be used for encrypting and decrypting data. But prior to this you must have updated your network settings. So, let’s use Azure CLI:
az keyvault key create --vault-name <YourKeyVaultName> --name <YourKeyName> --protection software
az keyvault key import --vault-name <YourKeyVaultName> --name <YourKeyName> --pem-file <YourPemFilePath>
Replace <YourKeyVaultName>, <YourKeyName>, and <YourPemFilePath> with your Key Vault name, the key name, and the path to your key file.
az keyvault set-policy --name <YourKeyVaultName> --spn <YourAppServicePrincipalId> --key-permissions get list wrapKey unwrapKey
Replace <YourKeyVaultName> with your Key Vault name and <YourAppServicePrincipalId> with the service principal ID of your Blazor app.
You will configure Azure Blob Storage to store the encrypted data. You will be using Azure Storage client libraries for Blob Storage or Queue Storage if that’s your choice with the same steps.
az storage account create --name <YourStorageAccountName> --resource-group <YourResourceGroup> --location <YourLocation> --sku Standard_LRS --kind StorageV2
Replace <YourStorageAccountName>, <YourResourceGroup>, and <YourLocation> with your desired storage account name, resource group, and Azure region.
az role assignment create --assignee <YourAppServicePrincipalId> --role "Storage Blob Data Contributor" --scope /subscriptions/<YourSubscriptionId>/resourceGroups/<YourResourceGroup>/providers/Microsoft.Storage/storageAccounts/<YourStorageAccountName>
Replace <YourAppServicePrincipalId>, <YourSubscriptionId>, <YourResourceGroup>, and <YourStorageAccountName> with your service principal ID, subscription ID, resource group, and storage account name.
az storage account generate-sas --account-name <YourStorageAccountName> --permissions rwl --expiry <YYYY-MM-DD> --resource-types sco --services b --https-only
If your app is using a managed identity, the role assignment from previous step, it will automatically secure the storage account access, and no additional steps are required.
Replace <YourStorageAccountName> and <YYYY-MM-DD> with your storage account name and desired expiry date.
pip install cryptography azure-storage-blob
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
import os
from base64 import urlsafe_b64encode
# Configuration
account_url = "https://<your_storage_account>.blob.core.windows.net"
container_name = "my-container"
blob_name = "my-encrypted-blob"
key = b'secret_key' # Replace with your secret key
# Function to encrypt data
def encrypt_data(data, key):
backend = default_backend()
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=backend
)
key = kdf.derive(key)
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=backend)
encryptor = cipher.encryptor()
encrypted_data = encryptor.update(data) + encryptor.finalize()
return salt + iv + encrypted_data
# Sample data to encrypt
data = b"Sensitive data that needs encryption"
# Encrypt the data
encrypted_data = encrypt_data(data, key)
# Upload encrypted data to Azure Blob Storage
blob_service_client = BlobServiceClient(account_url=account_url)
blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob_name)
blob_client.upload_blob(encrypted_data, overwrite=True)
print("Data encrypted and uploaded successfully.")
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from azure.storage.blob import BlobServiceClient, BlobClient, ContainerClient
import os
# Configuration
account_url = "https://<your_storage_account>.blob.core.windows.net"
container_name = "my-container"
blob_name = "my-encrypted-blob"
key = b'secret_key' # Replace with your secret key
# Function to decrypt data
def decrypt_data(encrypted_data, key):
backend = default_backend()
salt = encrypted_data[:16]
iv = encrypted_data[16:32]
ciphertext = encrypted_data[32:]
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=100000,
backend=backend
)
key = kdf.derive(key)
cipher = Cipher(algorithms.AES(key), modes.CFB(iv), backend=backend)
decryptor = cipher.decryptor()
decrypted_data = decryptor.update(ciphertext) + decryptor.finalize()
return decrypted_data
# Download encrypted data from Azure Blob Storage
blob_service_client = BlobServiceClient(account_url=account_url)
blob_client = blob_service_client.get_blob_client(container=container_name, blob=blob_name)
encrypted_data = blob_client.download_blob().readall()
# Decrypt the data
decrypted_data = decrypt_data(encrypted_data, key)
print(f"Decrypted data: {decrypted_data.decode('utf-8')}")
dotnet add package Azure.Storage.Blobs
dotnet add package System.Security.Cryptography.Algorithms
using System;
using System.IO;
using System.Security.Cryptography;
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
// Configuration
string accountUrl = "https://<your_storage_account>.blob.core.windows.net";
string containerName = "my-container";
string blobName = "my-encrypted-blob";
byte[] key = Convert.FromBase64String("your_base64_secret_key"); // Replace with your Base64 encoded secret key
// Function to encrypt data
byte[] EncryptData(byte[] data, byte[] key)
{
using var aes = Aes.Create();
aes.Key = key;
aes.GenerateIV();
using var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using var ms = new MemoryStream();
ms.Write(aes.IV, 0, aes.IV.Length);
using (var cs = new CryptoStream(ms, encryptor, CryptoStreamMode.Write))
{
cs.Write(data, 0, data.Length);
}
return ms.ToArray();
}
// Sample data to encrypt
byte[] data = System.Text.Encoding.UTF8.GetBytes("Sensitive data that needs encryption");
// Encrypt the data
byte[] encryptedData = EncryptData(data, key);
// Upload encrypted data to Azure Blob Storage
BlobServiceClient blobServiceClient = new BlobServiceClient(new Uri(accountUrl));
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
BlobClient blobClient = containerClient.GetBlobClient(blobName);
using (var ms = new MemoryStream(encryptedData))
{
blobClient.Upload(ms, new BlobHttpHeaders { ContentType = "application/octet-stream" }, true);
}
Console.WriteLine("Data encrypted and uploaded successfully.");
using System;
using System.IO;
using System.Security.Cryptography;
using Azure.Storage.Blobs;
// Configuration
string accountUrl = "https://<your_storage_account>.blob.core.windows.net";
string containerName = "my-container";
string blobName = "my-encrypted-blob";
byte[] key = Convert.FromBase64String("your_base64_secret_key"); // Replace with your Base64 encoded secret key
// Function to decrypt data
byte[] DecryptData(byte[] encryptedData, byte[] key)
{
using var aes = Aes.Create();
using var ms = new MemoryStream(encryptedData);
byte[] iv = new byte[aes.BlockSize / 8];
ms.Read(iv, 0, iv.Length);
using var decryptor = aes.CreateDecryptor(key, iv);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var result = new MemoryStream();
cs.CopyTo(result);
return result.ToArray();
}
// Download encrypted data from Azure Blob Storage
BlobServiceClient blobServiceClient = new BlobServiceClient(new Uri(accountUrl));
BlobContainerClient containerClient = blobServiceClient.GetBlobContainerClient(containerName);
BlobClient blobClient = containerClient.GetBlobClient(blobName);
using var ms = new MemoryStream();
blobClient.DownloadTo(ms);
byte[] encryptedData = ms.ToArray();
// Decrypt the data
byte[] decryptedData = DecryptData(encryptedData, key);
Console.WriteLine($"Decrypted data: {System.Text.Encoding.UTF8.GetString(decryptedData)}");
Replace <your_storage_account> and your_base64_secret_key with your actual storage account URL and Base64 encoded secret key, respectively.
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.16.0</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Security;
import java.util.Base64;
public class EncryptAndUpload {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ACCOUNT_URL = "https://<your_storage_account>.blob.core.windows.net";
private static final String CONTAINER_NAME = "my-container";
private static final String BLOB_NAME = "my-encrypted-blob";
private static final String SECRET_KEY = "your_base64_secret_key"; // Replace with your Base64 encoded secret key
public static void main(String[] args) throws Exception {
// Sample data to encrypt
byte[] data = "Sensitive data that needs encryption".getBytes();
// Encrypt the data
byte[] encryptedData = encryptData(data);
// Upload encrypted data to Azure Blob Storage
BlobServiceClient blobServiceClient = new BlobServiceClientBuilder().endpoint(ACCOUNT_URL).buildClient();
BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(CONTAINER_NAME);
BlobClient blobClient = containerClient.getBlobClient(BLOB_NAME);
Path tempFile = Files.createTempFile("encrypted", ".dat");
Files.write(tempFile, encryptedData);
blobClient.uploadFromFile(tempFile.toString(), true);
System.out.println("Data encrypted and uploaded successfully.");
}
private static byte[] encryptData(byte[] data) throws Exception {
SecretKey key = getKeyFromBase64(SECRET_KEY);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
byte[] iv = new byte[cipher.getBlockSize()];
new java.security.SecureRandom().nextBytes(iv);
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
byte[] encryptedData = cipher.doFinal(data);
byte[] ivAndEncryptedData = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, ivAndEncryptedData, 0, iv.length);
System.arraycopy(encryptedData, 0, ivAndEncryptedData, iv.length, encryptedData.length);
return ivAndEncryptedData;
}
private static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return new javax.crypto.spec.SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
}
import com.azure.storage.blob.BlobClient;
import com.azure.storage.blob.BlobContainerClient;
import com.azure.storage.blob.BlobServiceClient;
import com.azure.storage.blob.BlobServiceClientBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Security;
import java.util.Base64;
public class DownloadAndDecrypt {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String ACCOUNT_URL = "https://<your_storage_account>.blob.core.windows.net";
private static final String CONTAINER_NAME = "my-container";
private static final String BLOB_NAME = "my-encrypted-blob";
private static final String SECRET_KEY = "your_base64_secret_key"; // Replace with your Base64 encoded secret key
public static void main(String[] args) throws Exception {
// Download encrypted data from Azure Blob Storage
BlobServiceClient blobServiceClient = new BlobServiceClientBuilder().endpoint(ACCOUNT_URL).buildClient();
BlobContainerClient containerClient = blobServiceClient.getBlobContainerClient(CONTAINER_NAME);
BlobClient blobClient = containerClient.getBlobClient(BLOB_NAME);
Path tempFile = Files.createTempFile("encrypted", ".dat");
blobClient.downloadToFile(tempFile.toString());
byte[] encryptedData = Files.readAllBytes(tempFile);
// Decrypt the data
byte[] decryptedData = decryptData(encryptedData);
System.out.println("Decrypted data: " + new String(decryptedData));
}
private static byte[] decryptData(byte[] encryptedData) throws Exception {
SecretKey key = getKeyFromBase64(SECRET_KEY);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
byte[] iv = new byte[cipher.getBlockSize()];
System.arraycopy(encryptedData, 0, iv, 0, iv.length);
byte[] encryptedContent = new byte[encryptedData.length - iv.length];
System.arraycopy(encryptedData, iv.length, encryptedContent, 0, encryptedContent.length);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(encryptedContent);
}
private static SecretKey getKeyFromBase64(String base64Key) {
byte[] decodedKey = Base64.getDecoder().decode(base64Key);
return new javax.crypto.spec.SecretKeySpec(decodedKey, 0, decodedKey.length, "AES");
}
}
Likewise, you will replace <your_storage_account> and your_base64_secret_key with your actual storage account URL and Base64 encoded secret key, respectively and ensure you are using the latest Azure SDK.
We have seen so far, how Client-Side Encryption (CSE) provides an enhanced level of security by ensuring that data is encrypted before it leaves the client’s environment, and how we can implement this with three different programing languages for Azure App using Azure Key Vault, Azure Blob and create Azure Storage Account. This method aligns well with regulatory requirements such as GDPR, PCI-DSS, and FERPA, offering greater control over sensitive information and protecting it from breaches and unauthorized access. By implementing CSE, can better meet security and regulatory requirements, ensuring the highest level of data protection. It helps provides enhanced protection against data breaches. While it involves more complexity than Client-Side Key Encryption (CSKE), it offers significant security benefits that are crucial for handling sensitive data. CSE enhances security by allowing you to manage encryption keys independently. It's a valuable practice for safeguarding sensitive information in cloud solutions!
CAUTION:
For more information on Azure's support for Client-Side Encryption and to access further resources on Azure's official documentation and security best practices.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.