怎样做才能让Java 序列化机制 更安全 ? Security principles we follow to make Java Serialization safe.
来源:互联网 发布:网络存储服务器 编辑:程序博客网 时间:2024/04/30 05:05
概述
Java 序列化 serialization,大家应该都不陌生。其主要职责就是将一个对象的状态转化为一个字节序列,以方便对象的持久化或网络传输。反序列化的过程正好相反。开发人员所要做的只是实现Serializable接口,然后调用ObjectOutputStream/ObjectInputStream的WriteObject/ReadObject方法即可,其他的工作 JVM 会自动帮你做了。
那通过实现Serializable 接口所获取的序列化能力是否有安全隐患?由于这些字节序列已经脱离了Java的安全体系存在于磁盘或网络上,我们能否对序列化后的字节序列进行查看和修改,甚至于注入恶意病毒呢? Java 反序列化机制是否又会对建立的对象进行验证以确保它的安全性、准确性呢? 如果你想到这些问题,那恐怕答案会让你失望了。Java序列化后的字节序列基本都是明文存在的,而且字节序列的组成有很明确的文档进行说明,你可以试着用一些十六进制的文本编辑工具,如Hexeditor 查看一下对象序列化后的内容,你都能看到很多私有变量的实际赋值。关于字节序列的说明,可参考对象序列化流协议 ,这里就不多说了。这篇文章的重点是说一些Java提供的安全机制,通过这些机制,我们能够提升序列化/反序列化的安全指数。
读这篇文章前,最好能了解一些Java序列化的基本知识。
Transient
这个关键字的用途,大家应该都不陌生。它用来指定可序列化对象中,哪个变量不被序列化。如果你的对象中存放了一些敏感信息,不想让别人看到的话。那么就把存放这个敏感信息的变量声明为Transient. 如下代码例子所示,Employee类中有一个私有变量_salary,我们在序列化时,想忽略这个敏感信息,那将它定义为transient即可。
import java.io.Serializable;public class Employee implements Serializable {private static final long serialVersionUID = -7331553489509930824L;private String _name;private transient double _salary;public Employee(String name,double salary) {this._name = name;this._salary = salary;}public String toString(){return "Employee Name: " + this._name + " with salary " + this._salary;}}
import java.io.*;public class SerializationTest {public void serialize() throws IOException{Employee em = new Employee("Matt",10000);FileOutputStream fos = null;ObjectOutputStream oos = null;try {fos = new FileOutputStream("employee.save");oos = new ObjectOutputStream(fos); System.out.println("Serialized - "+ em.toString()); oos.writeObject(em);}finally{try {oos.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public void deSerialize() throws ClassNotFoundException, IOException {FileInputStream fis = null;ObjectInputStream ois = null;try { fis = new FileInputStream("employee.save"); ois = new ObjectInputStream(fis); Employee e = (Employee) ois.readObject(); System.out.println("Deserialized - "+ e.toString());}finally{try {ois.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void main(String[] args) throws IOException, ClassNotFoundException {SerializationTest st = new SerializationTest();st.serialize();st.deSerialize();}}
输出结果如下
Serialized - Employee Name: Matt with salary 10000.0Deserialized - Employee Name: Matt with salary 0.0
这说明_salary 变量的值没有被序列化。
WirteObject & ReadObject
的类来说是可选方法。如果实现了,那么在序列化/反序列化的时候,会调用。否则,默认的序列化/反序列化将被执行。在这两个方法里,只需要关心方法所在类本身的字段域,不需要对其父类或子类负责。在这两个方法里,我们还是需要调用ObjectOutputStream/
ObjectInputStream 的方法
defaultWriteObject/defaultReadObject 以执行Java的默认序列化/反序列化过程。如下例所示,其中 SerializationTest 类与上例相比,没有变化,故省略。 Employee类如下import java.io.Serializable;public class Employee implements Serializable {private static final long serialVersionUID = -7331553489509930824L;private String _name;private double _salary;public Employee(String name,double salary) {this._name = name;this._salary = salary;} private void writeObject(java.io.ObjectOutputStream stream) throws java.io.IOException{ _salary = _salary * _name.hashCode(); //只做实例,可以使用任何你认为合适的加密算法。 stream.defaultWriteObject(); System.out.println("Customized writeObject method called.");}private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException{ stream.defaultReadObject(); _salary = _salary / _name.hashCode(); //只做实例,可以使用任何你认为合适的解密算法。 System.out.println("Customized readObject method called.");}public String toString(){return "Employee Name: " + this._name + " with salary " + this._salary;}}
Serialized - Employee Name: Matt with salary 10000.0Customized writeObject method called.Customized readObject method called.Deserialized - Employee Name: Matt with salary 10000.0
SealedObject & SignedObject
import java.io.Serializable;public class Employee implements Serializable {private static final long serialVersionUID = -7331553489509930824L;private String _name;private double _salary;public Employee(String name,double salary) {this._name = name;this._salary = salary;} /* * 通过实现writeReplace方法来自动返回一个替代的SealedObject对象不可行,会导致栈溢出。因为SealedObject会对传入的待加密对象进行深Copy。这个操作就是通过序列化完成的。所以,会递归成死循环。 */ /*private Object writeReplace()throws java.io.ObjectStreamException{ SealedObject so = null; try {so = new SealedObject(this, new NullCipher());} catch (IllegalBlockSizeException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} return so;}*/public String toString(){return "Employee Name: " + this._name + " with salary " + this._salary;}}
import java.io.*;import java.security.InvalidKeyException;import java.security.Key;import java.security.NoSuchAlgorithmException;import javax.crypto.BadPaddingException;import javax.crypto.Cipher;import javax.crypto.IllegalBlockSizeException;import javax.crypto.KeyGenerator;import javax.crypto.NoSuchPaddingException;import javax.crypto.NullCipher;import javax.crypto.SealedObject;import javax.crypto.SecretKey;public class SerializationTest {private static Key _key = null;public void serialize() throws IOException, IllegalBlockSizeException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException{Employee em = new Employee("Matt",10000);FileOutputStream fos = null;ObjectOutputStream oos = null;try {fos = new FileOutputStream("employee.save");oos = new ObjectOutputStream(fos);KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede"); _key = keyGenerator.generateKey(); Cipher cipher = Cipher.getInstance("DESede"); cipher.init(Cipher.ENCRYPT_MODE, _key);SealedObject so = new SealedObject(em,cipher);oos.writeObject(so); System.out.println("Serialized - "+ em.toString());}finally{try {oos.close();} catch (IOException e) {e.printStackTrace();}}}public void deSerialize() throws ClassNotFoundException, IOException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {FileInputStream fis = null;ObjectInputStream ois = null;try { fis = new FileInputStream("employee.save"); ois = new ObjectInputStream(fis); SealedObject so = (SealedObject)ois.readObject(); Employee e = (Employee) so.getObject(_key); System.out.println("Deserialized - "+ e.toString());}finally{try {ois.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {SerializationTest st = new SerializationTest();st.serialize();st.deSerialize();}}
Validation
import java.io.InvalidObjectException;import java.io.ObjectInputValidation;import java.io.Serializable;public class Employee implements Serializable,ObjectInputValidation {private static final long serialVersionUID = -7331553489509930824L;private String _name;private double _salary;public Employee(String name,double salary) {this._name = name;this._salary = salary;}public String toString(){return "Employee Name: " + this._name + " with salary " + this._salary;}private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException{ stream.defaultReadObject(); stream.registerValidation(this, 0); System.out.println("Customized readObject method called.");}@Overridepublic void validateObject() throws InvalidObjectException {System.out.println("Validation object after deserialization.");if (_salary < 0) throw new InvalidObjectException("The Deserialized object is invalid. Salary can't be negative.");elseSystem.out.println("The Deserialized object is valid.");}}
import java.io.*;public class SerializationTest {public void serialize() throws IOException{Employee em = new Employee("Matt",10000);FileOutputStream fos = null;ObjectOutputStream oos = null;try {fos = new FileOutputStream("employee.save");oos = new ObjectOutputStream(fos); System.out.println("Serialized - "+ em.toString()); oos.writeObject(em);}finally{try {oos.close();} catch (IOException e) {e.printStackTrace();}}}public void deSerialize() throws ClassNotFoundException, IOException {FileInputStream fis = null;ObjectInputStream ois = null;try { fis = new FileInputStream("employee.save"); ois = new ObjectInputStream(fis); Employee e = (Employee) ois.readObject(); System.out.println("Deserialized - "+ e.toString());}finally{try {ois.close();} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) throws IOException, ClassNotFoundException {SerializationTest st = new SerializationTest();st.serialize();st.deSerialize();}}
Serialized - Employee Name: Matt with salary 10000.0Customized readObject method called.Validation object after deserialization.The Deserialized object is valid.Deserialized - Employee Name: Matt with salary 10000.0
如果你想看到验证失败的结果,你可以把SerializationTest类中的代码
Employee em = new Employee("Matt",10000);
改为
Employee em = new Employee("Matt",-10000);
输出结果如下
Serialized - Employee Name: Matt with salary -10000.0Customized readObject method called.Validation object after deserialization.Exception in thread "main" java.io.InvalidObjectException: The Deserialized object is invalid. Salary can't be negative.at com.tr.serialization.validation.Employee.validateObject(Employee.java:37)at java.io.ObjectInputStream$ValidationList$1.run(ObjectInputStream.java:2206)at java.security.AccessController.doPrivileged(Native Method)at java.io.ObjectInputStream$ValidationList.doCallbacks(ObjectInputStream.java:2202)at java.io.ObjectInputStream.readObject(ObjectInputStream.java:357)at com.tr.serialization.validation.SerializationTest.deSerialize(SerializationTest.java:38)at com.tr.serialization.validation.SerializationTest.main(SerializationTest.java:55)
Say NO to 序列化
import java.io.Serializable;public class Employee implements Serializable {private static final long serialVersionUID = -7331553489509930824L;private String _name;private double _salary;public Employee(String name,double salary) {this._name = name;this._salary = salary;}public String toString(){return "Employee Name: " + this._name + " with salary " + this._salary;}}import java.io.NotSerializableException;public class PartTimeEmployee extends Employee { private int working_days_each_month;private double salary_each_hour;public PartTimeEmployee(String name, double salary) {super(name, salary);// TODO Auto-generated constructor stub} private void writeObject(java.io.ObjectOutputStream stream) throws java.io.IOException{ throw new NotSerializableException("This class is not serializable");}private void readObject(java.io.ObjectInputStream stream) throws java.io.IOException, ClassNotFoundException{throw new NotSerializableException("This class is not serializable");}}
import java.io.*;public class SerializationTest {public void serialize() throws IOException{PartTimeEmployee em = new PartTimeEmployee("Matt",10000);FileOutputStream fos = null;ObjectOutputStream oos = null;try {fos = new FileOutputStream("employee.save");oos = new ObjectOutputStream(fos); oos.writeObject(em); System.out.println("Serialized - "+ em.toString());}finally{try {oos.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public void deSerialize() throws ClassNotFoundException, IOException {FileInputStream fis = null;ObjectInputStream ois = null;try { fis = new FileInputStream("employee.save"); ois = new ObjectInputStream(fis); PartTimeEmployee e = (PartTimeEmployee) ois.readObject(); System.out.println("Deserialized - "+ e.toString());}finally{try {ois.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public static void main(String[] args) throws IOException, ClassNotFoundException {SerializationTest st = new SerializationTest();st.serialize();st.deSerialize();}}
Exception in thread "main" java.io.NotSerializableException: This class is not serializableat com.tr.serialization.no.PartTimeEmployee.writeObject(PartTimeEmployee.java:20)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)at java.lang.reflect.Method.invoke(Method.java:597)at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:940)at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1469)at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1400)at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1158)at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:330)at com.tr.serialization.no.SerializationTest.serialize(SerializationTest.java:18)at com.tr.serialization.no.SerializationTest.main(SerializationTest.java:56)
总结
- 怎样做才能让Java 序列化机制 更安全 ? Security principles we follow to make Java Serialization safe.
- java之序列化Serialization 机制
- Java Serialization 序列化
- 回首Java——Java序列化机制(Serialization,Deserialization)
- java serialization--java序列化
- Java的序列化(Serialization)
- java中的序列化 serialization
- 初探Java序列化(Serialization)
- Java 的序列化 (Serialization)
- java的Serialization机制
- java的Serialization 机制
- java的Serialization 机制
- java的Serialization 机制
- Java Serialization/序列化/反序列化
- Java Serialization/序列化/反序列化
- Java之一:Serialization(序列化…
- Java对象序列化(Object Serialization)
- Java 的序列化 (Serialization) 教程
- Document:getElementsByName()用法及范例
- Day07_for多种写法以及while.do用法
- 理解java中的ThreadLocal
- Day08_数组
- ThinkPHP Session跨域设置
- 怎样做才能让Java 序列化机制 更安全 ? Security principles we follow to make Java Serialization safe.
- Day09_变量~函数
- 线性判别分析(Linear Discriminant Analysis, LDA)算法分析
- OCP-1Z0-053-V12.02-20题
- 善用eclipse模板功能,有效提高編碼效率
- 把文件存储到sdcard中
- grails学习笔记-文件上传
- cclayer的区域
- 主题模型-LDA浅析