使用JPA侦听器的数据库加密
最近,我不得不將數(shù)據(jù)庫加密添加到幾個(gè)字段中,并且發(fā)現(xiàn)了很多不好的建議。
建筑問題
最大的問題是建筑。 如果持久性管理器悄悄地處理您的加密,那么根據(jù)定義,您的體系結(jié)構(gòu)將在持久性和安全性設(shè)計(jì)之間要求緊密而不必要的綁定。 您不能觸摸一個(gè)而不接觸另一個(gè)。
這似乎是不可避免的,但是有一個(gè)受人尊敬的想法,那就是最好的架構(gòu)是您擁有獨(dú)立的應(yīng)用程序開發(fā)人員和安全開發(fā)人員團(tuán)隊(duì)的架構(gòu)。 應(yīng)用程序開發(fā)人員不能草率,但總的來說,他們唯一的重點(diǎn)是功能完成。 安全開發(fā)人員負(fù)責(zé)設(shè)計(jì)和實(shí)現(xiàn)安全性。 唯一考慮這兩個(gè)方面的地方是建筑和頂層設(shè)計(jì)。
過去這不是很實(shí)用,但是面向方面的編程(AOP)和類似的概念已經(jīng)改變了這一點(diǎn)。 現(xiàn)在,在服務(wù)層和持久層之間注入一個(gè)攔截器是完全合理的,這樣就可以悄悄地丟棄未授權(quán)調(diào)用方查看的值。 10個(gè)項(xiàng)目的列表可能會(huì)減少到7個(gè),或者更新可能會(huì)引發(fā)異常,而不是修改只讀值。 持久保存集合時(shí)要復(fù)雜一些,但是一般方法應(yīng)該很明確。
這里的關(guān)鍵是,應(yīng)用程序開發(fā)人員無需查看安全代碼。 所有這些都可以通過在部署時(shí)通過配置文件添加的AOP注入來處理。 更重要的是,它可以隨時(shí)更改,而無需修改應(yīng)用程序本身。 (您可能需要執(zhí)行一個(gè)更新過程,該過程將更改數(shù)據(jù)庫中的值。)
攔截器甚至可以阻止對未記錄方法的調(diào)用-不用擔(dān)心流氓程序員。
在實(shí)踐中,許多站點(diǎn)將有幾個(gè)開發(fā)人員都戴上帽子,而不是擁有專門的安全團(tuán)隊(duì)。 只要他們能夠牢記自己的職責(zé),這不是問題。
在JPA或Hibernate字段中進(jìn)行透明加密絕對比在POJO中放入加密/解密代碼更好,但是它仍然在安全性和持久性層之間強(qiáng)加了不必要的綁定。 它還存在嚴(yán)重的安全問題。
安全問題
每當(dāng)您處理加密時(shí),都會(huì)遇到一個(gè)關(guān)鍵問題–可以將此對象寫入磁盤嗎? 最明顯的威脅是序列化,例如,通過鈍化數(shù)據(jù)以釋放內(nèi)存或?qū)⑵溥w移到其他服務(wù)器的應(yīng)用服務(wù)器。
實(shí)際上,這意味著您的密鑰和純文本內(nèi)容必須標(biāo)記為“ transient”(對于序列化引擎)和“ @Transient”(對于JPA或Hibernate)。 如果您真的很偏執(zhí),您甚至?xí)采w隱式序列化方法writeObject,因此可以絕對保證這些字段永遠(yuǎn)不會(huì)寫入磁盤。
這是可行的……但是它使透明的加密/解密大為失敗,因?yàn)樵摯a的全部目的是使這些字段看起來就像另一個(gè)字段。 您必須維護(hù)兩個(gè)字段-持久加密值和瞬態(tài)未加密值-并具有某種使它們保持同步的方法。 無需在您的pojo中添加任何密碼即可完成所有操作。
一個(gè)更微妙的問題是,如果攻擊者可以通過使應(yīng)用服務(wù)器崩潰而觸發(fā)核心轉(zhuǎn)儲(chǔ),則您的對象仍可能寫入磁盤。 細(xì)心的站點(diǎn)管理員將禁用核心轉(zhuǎn)儲(chǔ),但許多人忽略了它。 解決這個(gè)問題比較困難,但是如果AOP可以在需要解密值的方法周圍立即解密/加密值,則有可能。 您的應(yīng)用程序不關(guān)心解密在哪里發(fā)生,只要它在需要時(shí)就被解密即可。 這是應(yīng)該留給安全團(tuán)隊(duì)的決策類型。
可以通過操作系統(tǒng)交換文件將對象寫入磁盤的第三種方式,但這應(yīng)該不是問題,因?yàn)榻粨Q文件現(xiàn)在通常已加密。
JPA實(shí)體偵聽器
一個(gè)解決方案是JPA EntityListeners或相應(yīng)的Hibernate類。 這些是偵聽器類,可以提供在數(shù)據(jù)庫對象創(chuàng)建,刪除或修改之前或之后調(diào)用的方法。
樣例代碼
使用一些示例代碼最容易看到這一點(diǎn)。 考慮一種情況,我們必須保留第三方站點(diǎn)的用戶密碼。 在這種情況下,我們必須使用加密,而不是哈希。
(注意:我懷疑這是Twitter第三方應(yīng)用程序所需的實(shí)際信息–僅用于說明目的。)
實(shí)體
/*** Conventional POJO. Following other conventions the sensitive* information is written to a secondary table in addition to being* encrypted.*/ @Entity @Table(name='twitter') @SecondaryTable(name='twitter_pw', pkJoinColumns=@PrimaryKeyJoinColumn(name='twitter_id')) @EntityListeners(TwitterUserPasswordListener.class) public class TwitterUser {private Integer id;private String twitterUserprivate String encryptedPassword;transient private String password;@Id@GeneratedValue(strategy = GenerationType.IDENTITY)public Integer getId() { return id; }@Column(name = 'twitter_user')public String getTwitterUser() { return twitterUser; }@Column(name = 'twitter_pw', table = 'twitter_pw')@Lobpublic String getEncryptedPassword() { return encryptedPassword; }@Transientpublic String getPassword() { return password; }// similar definitions for setters.... }
DAO
EntityListener
為了在持久層和安全層之間保持清晰的隔離,偵聽器除了調(diào)用處理加密的服務(wù)外什么也不做。 它完全不了解加密細(xì)節(jié)。
public class TwitterUserPasswordListener {@Injectprivate EncryptorBean encryptor;/*** Decrypt password after loading.*/@PostLoad@PostUpdatepublic void decryptPassword(Object pc) {if (!(pc instanceof TwitterUser)) {return;}TwitterUser user = (TwitterUser) pc;user.setPassword(null);if (user.getEncryptedPassword() != null) {user.setPassword(encryptor.decryptString(user.getEncryptedPassword());}}/*** Decrypt password before persisting*/@PrePersist@PreUpdatepublic void encryptPassword(Object pc) {if (!(pc instanceof TwitterUser)) {return;}TwitterUser user = (TwitterUser) pc;user.setEncryptedPassword(null);if (user.getPassword() != null) {user.setEncryptedPassword(encryptor.encryptString(user.getPassword());}} }
EncryptorBean
EncryptorBean處理加密,但不知道正在加密什么。 這是一個(gè)最小的實(shí)現(xiàn)–在實(shí)踐中,我們可能會(huì)希望除了密文/明文之外還傳遞一個(gè)keyId。 這將使我們能夠以最小的干擾安靜地旋轉(zhuǎn)加密密鑰-這是通常的“簡單加密”方法絕對不可能實(shí)現(xiàn)的。
此類使用OWASP / ESAPI進(jìn)行加密,因?yàn)?)它應(yīng)已由您的應(yīng)用程序使用; 2)可移植格式允許其他應(yīng)用程序使用我們的數(shù)據(jù)庫,只要它們也使用OWASP / ESAPI庫即可。
該實(shí)現(xiàn)僅涵蓋字符串-健壯的解決方案應(yīng)具有針對所有原始類型以及可能針對特定領(lǐng)域的類(例如信用卡)的方法。
import org.owasp.esapi.ESAPI; import org.owasp.esapi.Encryptor; import org.owasp.esapi.codecs.Base64; import org.owasp.esapi.crypto.CipherText; import org.owasp.esapi.crypto.PlainText; import org.owasp.esapi.errors.EncryptionException; import org.owasp.esapi.reference.crypto.JavaEncryptor;@Stateless public class EncryptorBean {private static final String PBE_ALGORITHM = 'PBEWITHSHA256AND128BITAES-CBC-BC';private static final String ALGORITHM = 'AES';// hardcoded for demonstration use. In production you might get the// salt from the filesystem and the password from a appserver JNDI value.private static final String SALT = 'WR9bdtN3tMHg75PDK9PoIQ==';private static final char[] PASSWORD = 'password'.toCharArray();// the keyprivate transient SecretKey key;/*** Constructor creates secret key. In production we may want* to avoid keeping the secret key hanging around in memory for* very long.*/public EncryptorBean() {try {// create the PBE keyKeySpec spec = new PBEKeySpec(PASSWORD, Base64.decode(SALT), 1024);SecretKey skey = SecretKeyFactory.getInstance(PBE_ALGORITHM).generateSecret(spec);// recast key as straightforward AES without padding.key = new SecretKeySpec(skey.getEncoded(), ALGORITHM);} catch (SecurityException ex) {// handle appropriately...}}/*** Decrypt String*/public String decryptString(String ciphertext) {String plaintext = null;if (ciphertext != null) {try {Encryptor encryptor = JavaEncryptor.getInstance();CipherText ct = CipherText.from PortableSerializedBytes(Base64.decode(ciphertext));plaintext = encryptor.decrypt(key, ct).toString();} catch (EncryptionException e) {// handle exception. Perhaps set value to null?}}return plaintext;}/*** Encrypt String*/public String encryptString(String plaintext) {String ciphertext= null;if (plaintext!= null) {try {Encryptor encryptor = JavaEncryptor.getInstance();CipherText ct = encryptor.encrypt(key, new PlaintText(plaintext));ciphertext = Base64.encodeBytes(ct.asPortableSerializedByteArray());} catch (EncryptionException e) {// handle exception. Perhaps set value to null?}}return ciphertext;} }
最后的想法
沒有理由為什么未加密字段和加密字段之間必須具有一對一的關(guān)系。 將相關(guān)字段捆綁為一個(gè)值是完全合理的-實(shí)際上,最好單獨(dú)加密每個(gè)字段。 這些值可以用CSV,XML,JSON甚至屬性文件表示。
參考: Invariant Properties博客中的JCG合作伙伴 Bear Giles 使用JPA偵聽器進(jìn)行數(shù)據(jù)庫加密 。
翻譯自: https://www.javacodegeeks.com/2012/11/database-encryption-using-jpa-listeners.html
總結(jié)
以上是生活随笔為你收集整理的使用JPA侦听器的数据库加密的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JavaOne 2012:NetBean
- 下一篇: PostgreSQL PL / java