自动加密可序列化的类
在Coursera安全性最高項(xiàng)目的驗(yàn)尸討論中提出了一個(gè)瘋狂的想法。 類(lèi)可以在序列化期間對(duì)其自身進(jìn)行加密嗎?
這主要是一項(xiàng)學(xué)術(shù)性的“假設(shè)”練習(xí)。 很難想到這樣一種情況,我們希望在持久性期間依靠對(duì)象自加密而不是使用顯式加密機(jī)制。 我只能確定一種情況,我們不能簡(jiǎn)單地使類(lèi)無(wú)法序列化:
HTTPSession鈍化
Appserver可以鈍化不活動(dòng)的HTTPSession,以節(jié)省空間或?qū)?huì)話從一臺(tái)服務(wù)器遷移到另一臺(tái)服務(wù)器。 這就是會(huì)話應(yīng)該只包含可序列化對(duì)象的原因。 (在可以安裝在單個(gè)服務(wù)器上的小型應(yīng)用程序中,通常會(huì)忽略此限制,但是如果需要擴(kuò)展或擴(kuò)展實(shí)現(xiàn),則會(huì)導(dǎo)致問(wèn)題。)
一種方法(也是首選方法?)是使會(huì)話在鈍化過(guò)程中將其自身寫(xiě)入數(shù)據(jù)庫(kù),并在激活過(guò)程中將其自身重新加載。 實(shí)際保留的唯一信息是重新加載數(shù)據(jù)所需的內(nèi)容,通常只是用戶ID。 這給HTTPSession實(shí)現(xiàn)增加了一些復(fù)雜性,但是有很多好處。 一個(gè)主要好處是確保敏感信息被加密很簡(jiǎn)單。
這不是唯一的方法,某些站點(diǎn)可能更喜歡使用標(biāo)準(zhǔn)序列化。 一些應(yīng)用服務(wù)器可能會(huì)將“實(shí)時(shí)”會(huì)話的序列化副本的副本保留在H2等嵌入式數(shù)據(jù)庫(kù)中。 謹(jǐn)慎的開(kāi)發(fā)人員可能希望確保敏感信息在序列化期間進(jìn)行加密,即使它永遠(yuǎn)不會(huì)發(fā)生。
注意:可以提出一個(gè)強(qiáng)烈的論點(diǎn),即敏感信息不應(yīng)該首先出現(xiàn)在會(huì)話中–僅在必要時(shí)檢索它,并在不再需要時(shí)安全地丟棄它。
該方法
我采用的方法基于有效Java中的序列化一章。 廣義上講,我們希望使用序列化代理來(lái)處理實(shí)際的加密。 該行為是:
| 序列化 | writeReplace() | 創(chuàng)建代理 | 不適用 |
| writeObject() | 拋出異常 | 將加密的內(nèi)容寫(xiě)入ObjectOutputStream | |
| 反序列化 | readObject() | 從ObjectInputStream讀取加密的內(nèi)容 | |
| readResolve() | 構(gòu)造受保護(hù)的類(lèi)對(duì)象 |
調(diào)用反序列化方法時(shí),受保護(hù)的類(lèi)引發(fā)異常的原因是,它防止了攻擊者生成的序列化對(duì)象的攻擊。 請(qǐng)參閱上述書(shū)籍中有關(guān)虛假字節(jié)流攻擊和內(nèi)部字段盜竊攻擊的討論。
這種方法有很大的局限性-如果沒(méi)有子類(lèi)重新實(shí)現(xiàn)代理,則無(wú)法擴(kuò)展該類(lèi)。 我認(rèn)為這不是實(shí)際問(wèn)題,因?yàn)樵摷夹g(shù)僅用于保護(hù)包含敏感信息的類(lèi),并且很少希望添加超出設(shè)計(jì)人員期望的方法的方法。
代理類(lèi)處理加密。 下面的實(shí)現(xiàn)顯示了使用隨機(jī)鹽(IV)和加密強(qiáng)消息摘要(HMAC)來(lái)檢測(cè)篡改。
編碼
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);}} }毋庸置疑,加密密鑰不應(yīng)如圖所示進(jìn)行硬編碼或緩存。 這是一條捷徑,讓我們可以專注于實(shí)施的細(xì)節(jié)。
密碼和消息摘要應(yīng)使用不同的密鑰。 如果使用相同的密鑰,則將嚴(yán)重?fù)p害系統(tǒng)的安全性。
任何生產(chǎn)系統(tǒng)都應(yīng)處理另外兩件事:密鑰輪換以及更改密碼和摘要算法。 前者可以通過(guò)在有效負(fù)載中添加“密鑰ID”來(lái)處理,后者可以通過(guò)綁定序列化版本號(hào)和密碼算法來(lái)處理。 例如,版本1使用標(biāo)準(zhǔn)AES,版本2使用AES-256。 解串器應(yīng)該能夠處理舊的加密密鑰和密碼(在合理范圍內(nèi))。
測(cè)試碼
測(cè)試代碼很簡(jiǎn)單。 它創(chuàng)建一個(gè)對(duì)象,對(duì)其進(jìn)行序列化,反序列化,然后將結(jié)果與原始值進(jìn)行比較。
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());} }最后的話
我不能過(guò)分強(qiáng)調(diào)–這主要是一種智力活動(dòng)。 像往常一樣,最大的問(wèn)題是密鑰管理,而不是加密,并且通過(guò)前者所需的工作水平,您可能可以更快地實(shí)現(xiàn)更傳統(tǒng)的解決方案。
在某些情況下,這可能仍然“足夠好”。 例如,您可能只需要在長(zhǎng)時(shí)間運(yùn)行的應(yīng)用程序期間保留數(shù)據(jù)。 在這種情況下,您可以在啟動(dòng)時(shí)創(chuàng)建隨機(jī)密鑰,并在程序結(jié)束后直接丟棄所有序列化的數(shù)據(jù)。
- 源代碼: https : //gist.github.com/beargiles/90182af6f332830a2e0e
翻譯自: https://www.javacodegeeks.com/2015/06/auto-encrypting-serializable-classes.html
總結(jié)
以上是生活随笔為你收集整理的自动加密可序列化的类的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 重要假说被推翻:寒武纪生命大爆发与地球磁
- 下一篇: servlet 3.0异步_Servle