阿里雲使用者在雲上部署IT資產,需要對敏感性資料進行加密保護。如果被加密的資料對象較大,則可以通過KMS的密碼運算API線上產生資料密鑰,用離線資料密鑰在本地加密大量資料。這類加密模式叫作信封加密。
背景資訊
典型的情境包括(但不限於):
對業務資料檔案的加密
對全磁碟資料加密
本文以加密本地檔案為例,介紹如何使用KMS實現對資料的信封加密,以及如何解密被信封加密的資料。
加密和解密的原理
使用KMS建立一個主要金鑰,使用主要金鑰產生一個資料密鑰,再使用資料密鑰在本地加解密資料。這種情境適用於大量資料的加解密。具體架構如下所示:
信封加密操作流程如下:
通過KMS控制台,或者調用CreateKey,建立一個使用者主要金鑰。
調用GenerateDataKey建立一個資料密鑰。KMS會返回一個明文的資料密鑰和一個密文的資料密鑰。
使用明文的資料祕密金鑰加密檔案,產生密文檔案,然後銷毀記憶體中的清除金鑰。
使用者將密文資料密鑰和密文檔案一同儲存到持久化存放裝置或服務中。
信封解密操作流程如下:
從本地檔案中讀取密文資料密鑰。
調用KMS服務的Decrypt,將加密過的密鑰解密為清除金鑰。
用清除金鑰為本機資料解密,再銷毀記憶體中的清除金鑰。
加密和解密的API
您可以調用以下KMS API,在本地對資料進行加解密。
API名稱 | 說明 |
建立使用者主要金鑰(CMK)。 | |
為指定使用者主要金鑰建立一個別名。 | |
線上產生資料密鑰,用指定CMK加密資料密鑰後,返回資料密鑰的密文和明文。 | |
解密KMS直接加密的資料(包括GenerateDataKey產生的資料密鑰的密文),不需要指定CMK。 |
加密和解密本地檔案
阿里雲帳號AccessKey擁有所有OpenAPI的存取權限,建議您使用RAM使用者進行API訪問或日常營運。強烈建議不要把AccessKey ID和AccessKey Secret儲存到工程代碼裡,否則可能導致AccessKey泄露,威脅您帳號下所有資源的安全。
本樣本以將AccessKey配置在環境變數ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的方式來實現身分識別驗證為例。
更多認證資訊配置方式,請參見管理訪問憑據。
不同作業系統的環境變數配置方法不同,具體操作,請參見在Linux、macOS和Windows系統配置環境變數。
您可以通過阿里雲CLI建立使用者主要金鑰,加密和解密本地檔案。
調用CreateKey,建立使用者主要金鑰。
aliyun kms CreateKey
預期輸出:
{ "KeyMetadata": { "CreationDate": "2019-04-08T07:45:54Z", "Description": "", "KeyId": "1234abcd-12ab-34cd-56ef-12345678****", "KeyState": "Enabled", "KeyUsage": "ENCRYPT/DECRYPT", "DeleteDate": "", "Creator": "151266687691****", "Arn": "acs:kms:cn-hangzhou:151266687691****:key/1234abcd-12ab-34cd-56ef-12345678****", "Origin": "Aliyun_KMS", "MaterialExpireTime": "" }, "RequestId": "2a37b168-9fa0-4d71-aba4-2077dd9e80df" }
(可選)給主要金鑰添加別名。
別名是使用者主要金鑰的可選標識。如果使用者不建立別名,也可以直接使用密鑰的ID。
aliyun kms CreateAlias --AliasName alias/Apollo/WorkKey --KeyId 1234abcd-12ab-34cd-56ef-12345678****
說明其中,
Apollo/WorkKey
表示Apollo
專案中的工作密鑰(當前被用於加密的密鑰)。您可以在後續範例程式碼中使用別名(alias/Apollo/WorkKey
)調用加密API。加密本地檔案。
範例程式碼中:
使用者主要金鑰:別名為
alias/Apollo/WorkKey
。明文資料檔案:./data/sales.csv。
輸出的密文資料檔案:./data/sales.csv.cipher。
#!/usr/bin/env python #coding=utf-8 import json import base64 from Crypto.Cipher import AES from aliyunsdkcore import client from aliyunsdkkms.request.v20160120 import GenerateDataKeyRequest def KmsGenerateDataKey(client, key_alias): request = GenerateDataKeyRequest.GenerateDataKeyRequest() request.set_accept_format('JSON') request.set_KeyId(key_alias) request.set_NumberOfBytes(32) response = json.loads(client.do_action(request)) datakey_encrypted = response["CiphertextBlob"] datakey_plaintext = response["Plaintext"] return (datakey_plaintext, datakey_encrypted) def ReadTextFile(in_file): file = open(in_file, 'r') content = file.read() file.close() return content def WriteTextFile(out_file, lines): file = open(out_file, 'w') for ln in lines: file.write(ln) file.write('\n') file.close() # Out file format (text) # Line 1: b64 encoded data key # Line 2: b64 encoded IV # Line 3: b64 encoded ciphertext # Line 4: b64 encoded authentication tag def LocalEncrypt(datakey_plaintext, datakey_encrypted, in_file, out_file): data_key_binary = base64.b64decode(datakey_plaintext) cipher = AES.new(data_key_binary, AES.MODE_EAX) in_content = ReadTextFile(in_file) ciphertext, tag = cipher.encrypt_and_digest(in_content) lines = [datakey_encrypted, base64.b64encode(cipher.nonce), base64.b64encode(ciphertext), base64.b64encode(tag)]; WriteTextFile(out_file, lines) clt = client.AcsClient(os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"),'Region-Id') key_alias = 'alias/Apollo/WorkKey' in_file = './data/sales.csv' out_file = './data/sales.csv.cipher' # Generate Data Key datakey = KmsGenerateDataKey(clt, key_alias) # Locally Encrypt the sales record LocalEncrypt(datakey[0], datakey[1], in_file, out_file)
解密本地檔案。
範例程式碼中:
密文資料檔案:./data/sales.csv.cipher。
輸出的明文資料檔案:./data/decrypted_sales.csv。
#!/usr/bin/env python #coding=utf-8 import json import base64 from Crypto.Cipher import AES from aliyunsdkcore import client from aliyunsdkkms.request.v20160120 import DecryptRequest def KmsDecrypt(client, ciphertext): request = DecryptRequest.DecryptRequest() request.set_accept_format('JSON') request.set_CiphertextBlob(ciphertext) response = json.loads(client.do_action(request)) return response.get("Plaintext") def ReadTextFile(in_file): file = open(in_file, 'r') lines = [] for ln in file: lines.append(ln) file.close() return lines def WriteTextFile(out_file, content): file = open(out_file, 'w') file.write(content) file.close() def LocalDecrypt(datakey, iv, ciphertext, tag, out_file): cipher = AES.new(datakey, AES.MODE_EAX, iv) data = cipher.decrypt_and_verify(ciphertext, tag).decode('utf-8') WriteTextFile(out_file, data) clt = client.AcsClient(os.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"),os.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"),'Region-Id') in_file = './data/sales.csv.cipher' out_file = './data/decrypted_sales.csv' # Read encrypted file in_lines = ReadTextFile(in_file) # Decrypt data key datakey = KmsDecrypt(clt, in_lines[0]) # Locally decrypt the sales record LocalDecrypt( base64.b64decode(datakey), base64.b64decode(in_lines[1]), # IV base64.b64decode(in_lines[2]), # Ciphertext base64.b64decode(in_lines[3]), # Authentication tag out_file )