2.序列化并不安全
让Java开发人员诧异并感到不快的是,序列化二进制格式完全编写在文档中,并且完全可逆。实际上,只需将二进制序列化流的内容转储到控制台,就足以看清类是什么样子,以及它包含什么内容。
这对于安全性有着不良影响。例如,当通过RMI进行远程方法调用时,通过连接发送的对象中的任何private字段几乎都是以明文的方式出现在套接字流中,这显然容易招致哪怕最简单的安全问题。
幸运的是,序列化允许“hook”序列化过程,并在序列化之前和反序列化之后保护(或模糊化)字段数据。可以通过在Serializable对象上提供一个writeObject方法来做到这一点。
模糊化序列化数据
假设Person类中的敏感数据是age字段。毕竟,女士忌谈年龄。我们可以在序列化之前模糊化该数据,将数位循环左移一位,然后在反序列化之后复位。(您可以开发更安全的算法,当前这个算法只是作为一个例子。)
为了“hook”序列化过程,我们将在Person上实现一个writeObject方法;为了“hook”反序列化过程,我们将在同一个类上实现一个readObject方法。重要的是这两个方法的细节要正确—如果访问修改方法、参数或名称不同于清单4中的内容,那么代码将不被察觉地失败,Person的age将暴露。
- 清单4.模糊化序列化数据
- publicclassPerson
- implementsjava.io.Serializable
- {
- publicPerson(Stringfn,Stringln,inta)
- {
- this.firstName=fn;this.lastName=ln;this.age=a;
- }
- publicStringgetFirstName(){returnfirstName;}
- publicStringgetLastName(){returnlastName;}
- publicintgetAge(){returnage;}
- publicPersongetSpouse(){returnspouse;}
- publicvoidsetFirstName(Stringvalue){firstName=value;}
- publicvoidsetLastName(Stringvalue){lastName=value;}
- publicvoidsetAge(intvalue){age=value;}
- publicvoidsetSpouse(Personvalue){spouse=value;}
- privatevoidwriteObject(java.io.ObjectOutputStreamstream)
- throwsjava.io.IOException
- {
- //"Encrypt"/obscurethesensitivedata
- ageage=age<<2;
- stream.defaultWriteObject();
- }
- privatevoidreadObject(java.io.ObjectInputStreamstream)
- throwsjava.io.IOException,ClassNotFoundException
- {
- stream.defaultReadObject();
- //"Decrypt"/de-obscurethesensitivedata
- ageage=age<<2;
- }
- publicStringtoString()
- {
- return"[Person:firstName="+firstName+
- "lastName="+lastName+
- "age="+age+
- "spouse="+(spouse!=null?spouse.getFirstName():"[null]")+
- "]";
- }
- privateStringfirstName;
- privateStringlastName;
- privateintage;
- privatePersonspouse;
- }
如果需要查看被模糊化的数据,总是可以查看序列化数据流/文件。而且,由于该格式被完全文档化,即使不能访问类本身,也仍可以读取序列化流中的内容。
3.序列化的数据可以被签名和密封
上一个技巧假设您想模糊化序列化数据,而不是对其加密或者确保它不被修改。当然,通过使用writeObject和readObject可以实现密码加密和签名管理,但其实还有更好的方式。
如果需要对整个对象进行加密和签名,最简单的是将它放在一个javax.crypto.SealedObject和/或java.security.SignedObject包装器中。两者都是可序列化的,所以将对象包装在SealedObject中可以围绕原对象创建一种“包装盒”。必须有对称密钥才能解密,而且密钥必须单独管理。同样,也可以将SignedObject用于数据验证,并且对称密钥也必须单独管理。结合使用这两种对象,便可以轻松地对序列化数据进行密封和签名,而不必强调关于数字签名验证或加密的细节。很简洁,是吧?