可变hashcode的隐患
为识别对象,JDK为每个Object类都定义了一个hashcode,Object的类的hashcode是根据对象的内存地址做hash算法得出来的,String类则自己重写了hashcode()方法,是根据字符串的每个字符做算法累加起来的,Integer在直接返回value的值。
而很多时候,对于应用系统的一些类(Java Bean),是要根据属性来计算hashcode而非内存地址,就像String类。所以会去覆盖Object的equals方法。其实重写就重写了,但是出于技术上的要求,JDK的一些数据结构Collection作为一个数据的容器,它需要唯一定位某个对象,而判定对象是否相等的标准是:1,hashcode是否相等;2,equals函数是否返回true。所以如果这些Java Bean用到这些Collection的话重写equals就必须同时重写hashcode了。
但这不是重点,重点是另外一个问题:可变的hashcode带来的BUG。
大师Ted给出了例子,如下:
import java.util.*;
public class Person
implements Iterable<Person>
{
public Person(String fn, String ln, int a, Person... kids)
{
this.firstName = fn; this.lastName = ln; this.age = a;
for (Person kid : kids)
children.add(kid);
}
// ...
public void setFirstName(String value) { this.firstName = value; }
public void setLastName(String value) { this.lastName = value; }
public void setAge(int value) { this.age = value; }
public int hashCode() {
return firstName.hashCode() & lastName.hashCode() & age;
}
// ...
private String firstName;
private String lastName;
private int age;
private List<Person> children = new ArrayList<Person>();
}
// MissingHash.java
import java.util.*;
public class MissingHash
{
public static void main(String[] args)
{
Person p1 = new Person("Ted", "Neward", 39);
Person p2 = new Person("Charlotte", "Neward", 38);
System.out.println(p1.hashCode());
Map<Person, Person> map = new HashMap<Person, Person>();
map.put(p1, p2);
p1.setLastName("Finkelstein");
System.out.println(p1.hashCode());
System.out.println(map.get(p1));
}
}
可以看到这个Person重写了hashcode,是属性的hashcode做与运算得出的结果。但是注意到,当p1对象作为key放入HashMap后,随后改变了lastName的属性,导致p1对象的hashcode发生了改变,从而System.out.println(map.get(p1));直接输出null。
所以可以看到,在重写hashcode的时候要特别注意可变的hashcode的影响,为了消除这种隐患,最好给属性加上final的限制。
序列化安全问题
java中序列化对象可以用户磁盘存储和网络传输对象等,但如果不经过任何处理则这些流都是明文的,具有安全隐患,我们通过模糊化处理或使用SealedObject加密等方法来保证安全,如下:
1,模糊化处理,如下:
JAVA对于序列化、反序列化的类是利用ObjectOutputStream、ObjectIntputStream实现,如果需要序列化的类没有writeObject和readObject的方法话,则会调用默认的序列化方式,所以只要重写这两个方法就可以实现自定义的方式来增加安全性等特殊处理(JDK应该是考虑到了安全等问题才给与扩展),如下:
package com.tedneward;
public class Person
implements java.io.Serializable
{
public Person(String fn, String ln, int a)
{
this.firstName = fn; this.lastName = ln; this.age = a;
}
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public int getAge() { return age; }
public Person getSpouse() { return spouse; }
public void setFirstName(String value) { firstName = value; }
public void setLastName(String value) { lastName = value; }
public void setAge(int value) { age = value; }
public void setSpouse(Person value) { spouse = value; }
private void writeObject(java.io.ObjectOutputStream stream)
throws java.io.IOException
{
// "Encrypt"/obscure the sensitive data
age = age << 2;
stream.defaultWriteObject();
}
private void readObject(java.io.ObjectInputStream stream)
throws java.io.IOException, ClassNotFoundException
{
stream.defaultReadObject();
// "Decrypt"/de-obscure the sensitive data
age = age >> 2;
}
private String firstName;
private String lastName;
private int age;
private Person spouse;
}
2,使用SealedObject对对象进行加密,然后序列化,如下:
import java.io.Serializable;
import java.security.Security;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SealedObject;
import javax.crypto.SecretKey;
public class MainClass {
public static void main(String args[]) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
SecretKey secretKey;
Cipher encrypter, decrypter;
secretKey = KeyGenerator.getInstance("DES").generateKey();// des加密
encrypter = Cipher.getInstance("DES");
encrypter.init(Cipher.ENCRYPT_MODE, secretKey);
decrypter = Cipher.getInstance("DES");
decrypter.init(Cipher.DECRYPT_MODE, secretKey);
MyClass cust, unsealed;
SealedObject sealed;
cust = new MyClass();
cust.name = "Paul";
cust.password = "password";
// Seal it, storing it in a SealedObject
sealed = (new SealedObject(cust, encrypter));
FileOutputStream fos = new FileOutputStream(args[0]);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(sealed);
oos.close();
// Try unsealing it
String algorithmName = sealed.getAlgorithm();
System.out.println(algorithmName);
unsealed = (MyClass) sealed.getObject(decrypter);
System.out.println("NAME: " + unsealed.name);
System.out.println("PASSWORD: " + unsealed.password);
}
}
class MyClass implements Serializable {
public String name;
public String password;
}