上一篇文章让我们简单理解了Java中序列化的知识,以及我们Serializable的使用过程。
需要的话请查看上一节内容:
Java序列化Serializable初识
那么我们今天先经行一个小实验。接着上一节的内容。实验内容如下:
(1)先将一个Person类序列化并写入文件,得到文件内容如下。
(2)篡改文件中的内容,将末尾的Jack改成Jeck。
(3)反序列化,得到Person并在控制台输出。
代码在此不展示了,看过上一节的人应该很容易实现该实验内容。根据实验结果发现我们的信息被篡改了,而我们并不知道。如果这发生在网络传输中想想有多可怕,广且根据我们上面的实验,篡改竟然如此容易。因此我们需要收到我们消息之后验证我们的消息有没有被篡改,要怎么办呢?这个时候就要用到密码学中的认证相关知识。这一块知识不太理解的请看下面这篇文章:
数据公钥加密和认证中的私钥公钥
该文章出自月光博客,感谢该博主的分享。
这个认证过程在Java中实现就要使用我们的SignedObject类了。
如果不熟悉认证过程的去看上边那篇文章,不然下面你看不懂的。下面摘自Java API SignedObject类。
SignedObject 是一个用来创建实际运行时对象的类,在检测不到这些对象的情况下,其完整性不会遭受损害。
更明确地说,SignedObject 包含另外一个 Serializable 对象,即(要)签名的对象及其签名。
签名对象是对原始对象的“深层复制”(以序列化形式)。一旦生成了副本,对原始对象的进一步操作就不再影响该副本。
底层签名算法是由传递给构造方法和 verify 方法的 Signature 对象指定。下面是签名的典型用法:
Signature signingEngine = Signature.getInstance(algorithm,provider);
SignedObject so = new SignedObject(myobject, signingKey,signingEngine);
下面是对验证的典型用法(已接收到 SignedObject so):
Signature verificationEngine = Signature.getInstance(algorithm, provider);
if (so.verify(publickey, verificationEngine))
try {
Object myobj = so.getObject();
} catch (java.lang.ClassNotFoundException e) {};
以下几点需要注意。首先,不需要初始化签名或验证引擎,因为它将在构造方法和 verify 方法中被重新初始化。其次,为了成功验证,指定的公钥必须是与用来生成 SignedObject 的私钥对应的公钥。
更为重要的是,出于灵活性考虑,构造方法和 verify 方法允许使用自定义的签名引擎,这样可以实现未作为加密提供者一部分正常安装的签名算法。不过,编程人员编写知道使用什么 Signature 引擎的校验器代码至关重要,因为将调用它自己的 verify 方法的实现来验证签名。换句话说,恶意 Signature 在尝试绕过安全检查的验证中会选择始终返回 true。
在所有算法中,签名算法可以是使用 DSA 和 SHA-1 的 NIST 标准 DSA。该算法使用与签名惯例相同的惯例来指定。例如,可以将使用 SHA-1 消息分类算法的 DSA 算法指定为 “SHA/DSA” 或 “SHA-1/DSA”(它们是等效的)。如果使用 RSA 标准,消息分类算法将有多种选择,例如,可将签名算法指定为 “MD2/RSA”、”MD5/RSA” 或 “SHA-1/RSA”。没有默认的算法名称,所以必须为其指定名称。
Cryptography Package Provider 的名称也是由构造方法和 verify 方法的 Signature 参数指定的。如果未指定提供者,则使用默认的提供者。每种安装都可以配置为将特定的提供者作为默认提供者。
为了方便的对文件读写我们,我们将对文件读写的操作再次封装:
IOObjectFile类:
public class IOObjectFile {
public static boolean outputObjectToFile(Serializable object,String file){
FileOutputStream fileOutputStream = null ;
ObjectOutputStream objectOutputStream = null ;
try {
fileOutputStream = new FileOutputStream(file);
objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
fileOutputStream.close();
objectOutputStream.close();
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static Object inputObjectFromFile(String file) throws IOException, ClassNotFoundException{
FileInputStream fileInputStream = null ;
ObjectInputStream objectInputStream = null ;
fileInputStream = new FileInputStream(file);
objectInputStream = new ObjectInputStream(fileInputStream);
Object object = objectInputStream.readObject();
objectInputStream.close();
fileInputStream.close();
return object;
}
}
Serializable的实现类Person:
public class Person implements Serializable{
private static final long serialVersionUID = 987882963008866333L;
private int age;
private String firstName;
private String lastName;
private Gender gender;
enum Gender{
MALE,FEMALE
}
public Person() {
super();
}
public Person(int age, String firstName, String lastName) {
super();
this.age = age;
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return "Person [age=" + age + ", firstName=" + firstName
+ ", lastName=" + lastName + ", gender=" + gender + "]";
}
}
主函数类:
public class SignMain {
public static void main(String[] args) {
Person jack = new Person(22,"Jack","Chai") ;
try {
// 1. 生成公钥私钥对
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
// 2. 输出key到文件
if(IOObjectFile.outputObjectToFile(publicKey, "storage\\publicKey.txt")){
System.out.println("输出publicKey成功");
}else{
System.out.println("输出publicKey失败");
}
// 3. 生成签名,并用SignedObject类包装
Signature signingEngine = Signature.getInstance("DSA");
SignedObject so = null;
so = new SignedObject(jack, privateKey, signingEngine);
// 4. 输出Object到文件
if (IOObjectFile.outputObjectToFile(so, "storage\\serializable.txt")) {
System.out.println("输出SignedObject成功");
}else{
System.out.println("输出SignedObject失败");
}
// 5. 从文件输入Key
PublicKey publicKey2 = null;
publicKey2 = (PublicKey) IOObjectFile.inputObjectFromFile("storage\\publicKey.txt");
// 6. 从文件输入SignedObject,用同样的算法获得签名,
// 在调用verify方法验证是否被篡改
SignedObject signedObject = (SignedObject)IOObjectFile.inputObjectFromFile("storage\\serializable.txt");
Signature verificationEngine = Signature.getInstance("DSA");
if (signedObject.verify(publicKey2, verificationEngine)){
try {
Person person = (Person) signedObject.getObject();
System.out.println(person.toString());
} catch (java.lang.ClassNotFoundException e) {
}
}else{
System.out.println("内容已被篡改");
}
} catch (NoSuchAlgorithmException e1) {
e1.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (SignatureException e) {
e.printStackTrace();
} catch (ClassNotFoundException | IOException e) {
e.printStackTrace();
}
}
}
运行结果:
最后验证成功并输出了我们的Person类。这时我们去篡改一下文件中的内容试一试。
依然是将Jack改为Jeck。
然后我们将上面向文件输出的代码注释掉。只读取认证,运行结果:
这时候篡改了信息之后,发生了错误,该异常表示什么呢?查看API解释为:
当从对象流中读取的控制信息与内部一致性检查相冲突时,抛出此异常。如此看来被篡改没那么容易了。 捕捉异常做处理:
SignedObject signedObject = null;
try {
signedObject = (SignedObject)IOObjectFile.inputObjectFromFile("storage\\serializable.txt");
} catch (StreamCorruptedException e) {
System.out.println("控制信息与内部一致性检查相冲突!");
return ;
}
运行结果:
通过这样的异常捕捉我们可以做其他处理。我还不能篡改之后使其不抛出异常,如果有哪位大神可以,求分享。基本上这就是SignedObject的作用。介绍这个类的文章很少,自己仅是查看文档摸索出的,如有错误请各位大神斧正。