序列化加密字段_自动加密可序列化的类
序列化加密字段
在Coursera安全頂峰項目的驗尸討論中提出了一個瘋狂的想法。 類可以在序列化期間自行加密嗎?
這主要是學術上的“假設”練習。 很難想到這樣一種情況,我們希望在持久性期間依靠對象自加密而不是使用顯式加密機制。 我只能確定一種情況,我們不能簡單地使類無法序列化:
HTTPSession鈍化
Appserver可以鈍化不活動的HTTPSession,以節省空間或將會話從一臺服務器遷移到另一臺服務器。 這就是會話應該只包含可序列化對象的原因。 (在可以安裝在單個服務器上的小型應用程序中,通常會忽略此限制,但是如果需要擴展或擴展實現,則會導致問題。)
一種方法(也是首選方法?)是使會話在鈍化過程中將自身寫入數據庫,并在激活過程中重新加載自身。 實際保留的唯一信息是重新加載數據所需的內容,通常只是用戶ID。 這給HTTPSession實現增加了一些復雜性,但是有很多好處。 一個主要好處是確保敏感信息被加密很簡單。
這不是唯一的方法,某些站點可能更喜歡使用標準序列化。 一些應用服務器可能會將“實時”會話的序列化副本的副本保留在H2等嵌入式數據庫中。 謹慎的開發人員可能希望確保敏感信息在序列化期間進行加密,即使它永遠不會發生。
注意:可以有一個很強的論點,那就是敏感信息不應首先出現在會話中–僅在必要時檢索它,并在不再需要時安全地丟棄它。
該方法
我采用的方法基于有效Java中的序列化一章。 從廣義上講,我們希望使用序列化代理來處理實際的加密。 該行為是:
| 序列化 | writeReplace() | 創建代理 | 不適用 |
| writeObject() | 拋出異常 | 將加密的內容寫入ObjectOutputStream | |
| 反序列化 | readObject() | 從ObjectInputStream讀取加密的內容 | |
| readResolve() | 構造受保護的類對象 |
調用反序列化方法時,受保護的類引發異常的原因是,它防止了攻擊者生成的序列化對象的攻擊。 請參閱上述書籍中有關虛假字節流攻擊和內部字段盜竊攻擊的討論。
這種方法有很大的局限性-如果沒有子類重新實現代理,則無法擴展該類。 我認為這不是實際問題,因為該技術僅用于保護包含敏感信息的類,并且很少希望添加超出設計人員期望的方法的方法。
代理類處理加密。 下面的實現顯示了使用隨機鹽(IV)和加密強消息摘要(HMAC)來檢測篡改。
代碼
public class ProtectedSecret implements Serializable {private static final long serialVersionUID = 1L;private final String secret;/*** Constructor.* * @param secret*/public ProtectedSecret(final String secret) {this.secret = secret;}/*** Accessor*/public String getSecret() {return secret;}/*** Replace the object being serialized with a proxy.* * @return*/private Object writeReplace() {return new SimpleProtectedSecretProxy(this);}/*** Serialize object. We throw an exception since this method should never be* called - the standard serialization engine will serialize the proxy* returned by writeReplace(). Anyone calling this method directly is* probably up to no good.* * @param stream* @return* @throws InvalidObjectException*/private void writeObject(ObjectOutputStream stream) throws InvalidObjectException {throw new InvalidObjectException("Proxy required");}/*** Deserialize object. We throw an exception since this method should never* be called - the standard serialization engine will create serialized* proxies instead. Anyone calling this method directly is probably up to no* good and using a manually constructed serialized object.* * @param stream* @return* @throws InvalidObjectException*/private void readObject(ObjectInputStream stream) throws InvalidObjectException {throw new InvalidObjectException("Proxy required");}/*** Serializable proxy for our protected class. The encryption code is based* on https://gist.github.com/mping/3899247.*/private static class SimpleProtectedSecretProxy implements Serializable {private static final long serialVersionUID = 1L;private String secret;private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";private static final String HMAC_ALGORITHM = "HmacSHA256";private static transient SecretKeySpec cipherKey;private static transient SecretKeySpec hmacKey;static {// these keys can be read from the environment, the filesystem, etc.final byte[] aes_key = "d2cb415e067c7b13".getBytes();final byte[] hmac_key = "d6cfaad283353507".getBytes();try {cipherKey = new SecretKeySpec(aes_key, "AES");hmacKey = new SecretKeySpec(hmac_key, HMAC_ALGORITHM);} catch (Exception e) {throw new ExceptionInInitializerError(e);}}/*** Constructor.* * @param protectedSecret*/SimpleProtectedSecretProxy(ProtectedSecret protectedSecret) {this.secret = protectedSecret.secret;}/*** Write encrypted object to serialization stream.* * @param s* @throws IOException*/private void writeObject(ObjectOutputStream s) throws IOException {s.defaultWriteObject();try {Cipher encrypt = Cipher.getInstance(CIPHER_ALGORITHM);encrypt.init(Cipher.ENCRYPT_MODE, cipherKey);byte[] ciphertext = encrypt.doFinal(secret.getBytes("UTF-8"));byte[] iv = encrypt.getIV();Mac mac = Mac.getInstance(HMAC_ALGORITHM);mac.init(hmacKey);mac.update(iv);byte[] hmac = mac.doFinal(ciphertext);// TBD: write algorithm id...s.writeInt(iv.length);s.write(iv);s.writeInt(ciphertext.length);s.write(ciphertext);s.writeInt(hmac.length);s.write(hmac);} catch (Exception e) {throw new InvalidObjectException("unable to encrypt value");}}/*** Read encrypted object from serialization stream.* * @param s* @throws InvalidObjectException*/private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException, InvalidObjectException {s.defaultReadObject();try {// TBD: read algorithm id...byte[] iv = new byte[s.readInt()];s.read(iv);byte[] ciphertext = new byte[s.readInt()];s.read(ciphertext);byte[] hmac = new byte[s.readInt()];s.read(hmac);// verify HMACMac mac = Mac.getInstance(HMAC_ALGORITHM);mac.init(hmacKey);mac.update(iv);byte[] signature = mac.doFinal(ciphertext);// verify HMACif (!Arrays.equals(hmac, signature)) {throw new InvalidObjectException("unable to decrypt value");}// decrypt dataCipher decrypt = Cipher.getInstance(CIPHER_ALGORITHM);decrypt.init(Cipher.DECRYPT_MODE, cipherKey, new IvParameterSpec(iv));byte[] data = decrypt.doFinal(ciphertext);secret = new String(data, "UTF-8");} catch (Exception e) {throw new InvalidObjectException("unable to decrypt value");}}/*** Return protected object.* * @return*/private Object readResolve() {return new ProtectedSecret(secret);}} }毋庸置疑,加密密鑰不應如圖所示進行硬編碼或可能甚至進行緩存。 這是一條捷徑,可以讓我們專注于實施的細節。
密碼和消息摘要應使用不同的密鑰。 如果使用相同的密鑰,則將嚴重損害系統的安全性。
任何生產系統中都應處理另外兩件事:密鑰輪換以及更改密碼和摘要算法。 前者可以通過在有效負載中添加“密鑰ID”來處理,后者可以通過綁定序列化版本號和密碼算法來處理。 例如,版本1使用標準AES,版本2使用AES-256。 解串器應能夠處理舊的加密密鑰和密碼(在合理范圍內)。
測試碼
測試代碼很簡單。 它創建一個對象,對其進行序列化,反序列化,然后將結果與原始值進行比較。
public class ProtectedSecretTest {/*** Test 'happy path'.*/@Testpublic void testCipher() throws IOException, ClassNotFoundException {ProtectedSecret secret1 = new ProtectedSecret("password");ProtectedSecret secret2;byte[] ser;// serialize objecttry (ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutput output = new ObjectOutputStream(baos)) {output.writeObject(secret1);output.flush();ser = baos.toByteArray();}// deserialize object.try (ByteArrayInputStream bais = new ByteArrayInputStream(ser); ObjectInput input = new ObjectInputStream(bais)) {secret2 = (ProtectedSecret) input.readObject();}// compare values.assertEquals(secret1.getSecret(), secret2.getSecret());}/*** Test deserialization after a single bit is flipped.*/@Test(expected = InvalidObjectException.class)public void testCipherAltered() throws IOException, ClassNotFoundException {ProtectedSecret secret1 = new ProtectedSecret("password");ProtectedSecret secret2;byte[] ser;// serialize objecttry (ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutput output = new ObjectOutputStream(baos)) {output.writeObject(secret1);output.flush();ser = baos.toByteArray();}// corrupt ciphertextser[ser.length - 16 - 1 - 3] ^= 1;// deserialize object.try (ByteArrayInputStream bais = new ByteArrayInputStream(ser); ObjectInput input = new ObjectInputStream(bais)) {secret2 = (ProtectedSecret) input.readObject();}// compare values.assertEquals(secret1.getSecret(), secret2.getSecret());} }最后的話
我不能過分強調–這主要是一種智力活動。 像往常一樣,最大的問題是密鑰管理,而不是密碼學,并且由于前者需要付出的努力,您可能可以更快地實現更傳統的解決方案。
在某些情況下,這可能仍然“足夠好”。 例如,您可能只需要在長時間運行的應用程序期間保留數據。 在這種情況下,您可以在啟動時創建隨機密鑰,并在程序結束后直接丟棄所有序列化的數據。
- 源代碼: https : //gist.github.com/beargiles/90182af6f332830a2e0e
翻譯自: https://www.javacodegeeks.com/2015/06/auto-encrypting-serializable-classes.html
序列化加密字段
總結
以上是生活随笔為你收集整理的序列化加密字段_自动加密可序列化的类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电表位置备案怎么弄(电表位置备案)
- 下一篇: (fdisk linux)