資料庫加密技術屬於主動防禦機制,可以防止明文儲存引起的資料泄密、突破邊界防護的外部駭客攻擊以及來自內部高許可權使用者的資料竊取,從根本上解決資料庫敏感性資料泄漏問題。通過加密SDK在用戶端加密的資料可以儲存在關聯式資料庫或非關聯式資料庫中。本文為您介紹資料庫敏感性資料加密的使用情境、原理和樣本。
使用情境
資料庫敏感性資料被拖庫後,避免因明文儲存導致的資料泄露。
通常情況下,資料庫中的資料是以明文形式進行儲存和使用的,一旦資料檔案(或備份檔案)丟失,可能引發嚴重的資料泄露問題。而在拖庫攻擊中,明文儲存的資料對於攻擊者同樣沒有任何秘密可言。此時您需要對資料進行加密,避免資料泄露。
對高許可權使用者,資料庫敏感性資料加密可以防範內部竊取資料造成的資料泄露。
資料庫加密可以提供獨立於資料庫系統自身許可權控制體系之外的增強許可權控制的能力,由專用的加密系統為資料庫中的敏感性資料設定存取權限,從而有效限制資料庫超級使用者或其他高許可權使用者對敏感性資料的訪問行為,保障資料安全。
加密和解密原理
加密原理
建立密鑰。
加密SDK向Key Management Service(Key Management Service)發送調用GenerateDataKey介面請求,申請一個資料密鑰(Data Key)。KMS返回資料密鑰以及資料密鑰密文(Encrypted Data Key)。
加密並儲存資料。
使用資料金鑰組資料進行加密,得到加密結果,進行Base64編碼。
將加密資料的Base64編碼儲存在資料庫中。
解密原理
檢索並解密資料。
從資料庫讀取密文。
將密文進行Base64解密,解析密文訊息。加密SDK調用KMS的Decrypt介面將資料密鑰進行解密,KMS返回資料密鑰給本地加密用戶端。
解密資料。加密SDK(Encryption SDK)使用資料金鑰組資料密文進行解密,得到未經處理資料。
樣本
加密SDK用於面嚮應用的資料庫加密,密鑰由KMS產生和管理。
通過以下Spring JPA和Python範例程式碼,可以實現對User表中email欄位的寫入加密和讀取解密。樣本中每個欄位使用一個資料密鑰,解密時設定了密鑰緩衝,對同一欄位進行多次查詢時會檢索緩衝中可用的資料密鑰。
為保證欄位能儲存加密後的欄位,需要對被加密欄位的長度進行擴容,擴容比例為3倍。
Spring JPA樣本
阿里雲帳號AccessKey擁有所有OpenAPI的存取權限,建議您使用RAM使用者進行API訪問或日常營運。強烈建議不要把AccessKey ID和AccessKey Secret儲存到工程代碼裡,否則可能導致AccessKey泄露,威脅您帳號下所有資源的安全。
本樣本以將AccessKey配置在環境變數ALIBABA_CLOUD_ACCESS_KEY_ID和ALIBABA_CLOUD_ACCESS_KEY_SECRET的方式來實現身分識別驗證為例。
更多認證資訊配置方式,請參見管理訪問憑據。
不同作業系統的環境變數配置方法不同,具體操作,請參見在Linux、macOS和Windows系統配置環境變數。
定義實體類
@Entity public class User { @Id @GeneratedValue private Long id; private String name; private String email; // getters and setters ... }定義UserRepository類
public interface UserRepository extends CrudRepository<User, Long> { }實現Spring JPA的AttributeConverter介面相關功能
EncryptionConverter調用加密SDK的介面,擷取資料密鑰,對指定的資料進行加密、解密。
@Converter public class EncryptionConverter implements AttributeConverter<String, String> { private static String ACCESS_KEY_ID = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_ID"); private static String ACCESS_KEY_SECRET = System.getenv("ALIBABA_CLOUD_ACCESS_KEY_SECRET"); private static String CMK_ARN = "acs:kms:RegionId:UserId:key/CmkId"; private static AliyunConfig config; static { config = new AliyunConfig(); config.withAccessKey(ACCESS_KEY_ID, ACCESS_KEY_SECRET); } @Override public String convertToDatabaseColumn(String plainText) { BaseDataKeyProvider dataKeyProvider = new DefaultDataKeyProvider(CMK_ARN); AliyunCrypto crypto = new AliyunCrypto(config); try { CryptoResult<byte[]> encryptResult = crypto.encrypt(dataKeyProvider, plainText.getBytes(StandardCharsets.UTF_8), Collections.singletonMap("sample", "context")); return Base64.getEncoder().encodeToString(encryptResult.getResult()); } catch (InvalidAlgorithmException e) { System.out.println("Failed."); System.out.println("Error message: " + e.getMessage()); } return null; } @Override public String convertToEntityAttribute(String cipherText) { BaseDataKeyProvider dataKeyProvider = new DefaultDataKeyProvider(CMK_ARN); AliyunCrypto crypto = new AliyunCrypto(config); // *** 設定快取資料主要金鑰 *** CryptoKeyManager ckm = new CachingCryptoKeyManager(new LocalDataKeyMaterialCache()); crypto.setCryptoKeyManager(ckm); try { CryptoResult<byte[]> decryptResult = crypto.decrypt(dataKeyProvider, Base64.getDecoder().decode(cipherText)); return new String(decryptResult.getResult(), StandardCharsets.UTF_8); } catch (InvalidAlgorithmException | UnFoundDataKeyException e) { e.printStackTrace(); } return null; } }添加@Convert註解
添加@Convert註解到需要加密的屬性(資料庫中的列)。
@Entity public class User { @Id @GeneratedValue private Long id; private String name; @Convert(converter = EncryptionConverter.class) private String email; // getters and setters ... }
Python樣本
實體定義
class User(object): def get_name(self): return self.name def set_name(self, value): self.name = value def get_email(self): return self.email def set_email(self, value): self.email = value實現資料庫操作函數
def user_add(user): # 串連資料庫。 conn = db_connection() # 得到一個可以執行SQL語句並且將結果作為字典返回的遊標。 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 定義要執行的SQL語句。 sql = 'insert into user(name, email) values(%s,%s);' # 執行SQL語句。 cursor.execute(sql, [user.name, user.email]) print("insert name: " + user.name) print("insert email: " + user.email) # 提交寫操作。 conn.commit() last_id = cursor.lastrowid # 關閉游標對象。 cursor.close() # 關閉資料庫連接。 conn.close() return last_id def user_select_by_id(id): # 串連資料庫。 conn = db_connection() # 得到一個可以執行SQL語句並且將結果作為字典返回的遊標。 cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 定義要執行的SQL語句。 sql = 'select * from user where id = %s;' # 執行SQL語句。 cursor.execute(sql, [id]) result = cursor.fetchone() print("select result: " + str(result)) user = User() user.__dict__ = result # 關閉游標對象。 cursor.close() # 關閉資料庫連接。 conn.close() return user實現資料加密、解密裝飾器(decorator)
def enc_convert(): def enc_(func): def wrapper(self, plain_text): provider = DefaultDataKeyProvider(AES_KEY_ARN) client = build_aliyun_crypto(False) cipher_text, enc_material = client.encrypt(provider, plain_text.encode("utf-8"), ENCRYPTION_CONTEXT) cipher_text_str = base64.standard_b64encode(cipher_text).decode("utf-8") f = func(self, cipher_text_str) return f return wrapper return enc_ def dec_convert(column_name): def dec_(func): def wrapper(self): user = self.__dict__ cipher_text = user.get(column_name) cipher_text_bytes = base64.standard_b64decode(cipher_text.encode("utf-8")) provider = DefaultDataKeyProvider(AES_KEY_ARN) client = build_aliyun_crypto(False) plain_text, dec_material = client.decrypt(provider, cipher_text_bytes) user[column_name] = bytes.decode(plain_text) result = User() result.__dict__ = user f = func(result) return f return wrapper return dec_為需要保護的資料欄位添加裝飾器
class User(object): def get_name(self): return self.name def set_name(self, value): self.name = value @dec_convert(column_name="email") def get_email(self): return self.email @enc_convert() def set_email(self, value): self.email = value
關於資料庫敏感性資料加密的更多樣本,請參見Spring JPA樣本和Python樣本。