最近在写一个实验记录工具. 需求是这样的, 实验记录应当能序列化导出并具有一定的安全性, 这一情景下的程序设计有两个问题:
1.java自带的可序列化接口通过调用对象的writeObject和readObject方法来实现读写(如没有定义这两个方法,则采用对应的缺省方法), 但在保存敏感数据方面, 这两个方法可能会明文保存部分内容, 导致安全性不够
2.实验记录中的分子结构使用了CDK工具中的org.openscience.cdk.AtomContainer类, 该类是不可序列化的, 但CDK中提供了将分子结构按照化学信息学中规定的编码方式通过输出流写出的方式(例如常用的.mol格式编码)
针对这两个问题, 我们给出了如下解决方案:
1.writeObject和readObject的手动实现中加入AES加密过程,先将内容加密
2.通过字节流来编码分子结构,对应的字节数组也进行加密.
总结一下:
针对某些情况(例如有约定的文件编码格式(如图片的jpeg格式,png格式,分子结构的mol格式等)),java不提供相应对象的序列化方法,这时通常在工具包中有其它读入/写出方法.在利用这些方法读写的过程中,我们可以截取对应的流,在手动实现writeObject和readObject的过程中进行加密/解密后写出/读入.
另请参考
https://blog.youkuaiyun.com/dnc8371/article/details/106704873
https://www.cnblogs.com/loong-hon/p/10145060.html
https://cdk.github.io/
附部分源码
import org.openscience.cdk.AtomContainer;
import org.openscience.cdk.depict.Depiction;
import org.openscience.cdk.depict.DepictionGenerator;
import org.openscience.cdk.exception.CDKException;
import org.openscience.cdk.io.MDLV2000Reader;
import org.openscience.cdk.io.MDLV2000Writer;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ByteLookupTable;
import java.awt.image.LookupOp;
import java.io.*;
import java.security.SecureRandom;
public class ReagentMolecule implements Serializable {
public final static int NAME=0, SHORT=1, STRUCTURE=2, CASRN=3; //模式: 名称/简称/结构式/CAS register number
public ReagentMolecule(String str, int mode){
switch (mode) {
case NAME:
str_name=str;
break;
case SHORT:
str_short=str;
break;
case STRUCTURE:
if(str.matches(".+\\.mol")){
try{
MDLV2000Reader molReader=new MDLV2000Reader(new FileInputStream(str));
molecule=new AtomContainer(); molReader.read(molecule);
}
catch(IOException|CDKException e){
molecule=null;
System.out.println("Unable to load molecule structure from path" + str);
}
}else{
System.out.println("Format not supported");
}
case CASRN:
str_cas=str;
break;
}
lbl_mol=new MLabel(); lbl_mol.setBounds(0,0,800,800);
}
AtomContainer molecule; //分子结构(即原子容器对象)//todo:包含不可序列化属性
private String str_name="", str_cas="", str_short="";//分子名称/cas注册号/简称
public void setName(String name){str_name=name;}
public void setCas(String cas){str_cas=cas;}
public void setShortName(String shortName){str_short=shortName;}
private static String accessKey="default key";
private void writeObject(final ObjectOutputStream out) throws Exception {
MDLV2000Writer mw=new MDLV2000Writer();
ByteArrayOutputStream bos=new ByteArrayOutputStream();
mw.setWriter(bos); mw.writeMolecule(molecule);
KeyGenerator keygen=KeyGenerator.getInstance("AES");
keygen.init(128, new SecureRandom(accessKey.getBytes()));
SecretKey original_key=keygen.generateKey();
byte [] raw=original_key.getEncoded();
SecretKey key=new SecretKeySpec(raw, "AES");
Cipher cipher=Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte [] molecule_temp=cipher.doFinal(bos.toByteArray());
BASE64Encoder encoder=new BASE64Encoder();
String molecule_encoded=encoder.encode(molecule_temp);
String name_encoded=encoder.encode(cipher.doFinal(str_name.getBytes()));
String cas_encoded=encoder.encode(cipher.doFinal(str_cas.getBytes()));
String short_encoded=encoder.encode(cipher.doFinal(str_short.getBytes()));
out.writeUTF(molecule_encoded);
out.writeUTF(name_encoded);
out.writeUTF(cas_encoded);
out.writeUTF(short_encoded);
}
private void readObject(final ObjectInputStream in) throws Exception{
KeyGenerator keygen=KeyGenerator.getInstance("AES");
keygen.init(128, new SecureRandom(accessKey.getBytes()));
SecretKey original_key=keygen.generateKey();
byte [] raw=original_key.getEncoded();
SecretKey key=new SecretKeySpec(raw, "AES");
Cipher cipher=Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
BASE64Decoder decoder=new BASE64Decoder();
MDLV2000Reader mr=new MDLV2000Reader();
mr.setReader(new ByteArrayInputStream(cipher.doFinal(decoder.decodeBuffer(in.readUTF()))));
molecule=new AtomContainer(); mr.read(molecule);
str_name=new String(cipher.doFinal(decoder.decodeBuffer(in.readUTF())),"utf-8");
str_cas=new String(cipher.doFinal(decoder.decodeBuffer(in.readUTF())),"utf-8");
str_short=new String(cipher.doFinal(decoder.decodeBuffer(in.readUTF())),"utf-8");
lbl_mol=new MLabel();
}
private int displayMode=0;//显示模式
public void setDisplayMode(int mode) throws Exception{
if(mode==NAME&&str_name!=null){displayMode=NAME;}
else if(mode==SHORT&&str_short!=null){displayMode=SHORT;}
else if(mode==STRUCTURE&&molecule!=null){displayMode=STRUCTURE;}
else if(mode==CASRN&&str_cas!=null){displayMode=CASRN;}
else throw new RuntimeException("display mode not supported");
}
private MLabel lbl_mol;
public MLabel getDisplayContainer(){return lbl_mol;}
class MLabel extends JLabel{
Image img_structure;
private MLabel(){
if(!molecule.isEmpty()){
DepictionGenerator generator=new DepictionGenerator();
generator.withSize(getWidth(),getHeight());
Depiction depiction= null;
try{depiction=generator.depict(molecule);}
catch(CDKException e){e.printStackTrace();}
BufferedImage img_temp=depiction.toImg();
byte[] reverse = new byte[256];
for (int j = 0; j < 256; j++) {
reverse[j] = (byte) (255 - j);
}
ByteLookupTable blut = new ByteLookupTable(0, reverse);
img_structure=new SerializableImage(img_temp.getWidth(),img_temp.getHeight(),img_temp.getType());
(new LookupOp(blut, null)).filter(img_temp, (BufferedImage) img_structure);
}
}
class SerializableImage extends BufferedImage implements Serializable{
public SerializableImage(int width, int height, int imageType) {
super(width, height, imageType);
}
}
@Override
public void paint(Graphics g){
try{
g.drawImage(img_structure,getX(),getY(),null);
}catch(Exception e){e.getStackTrace();}
}
}
public static void main(String[] args) throws Exception{
ReagentMolecule md=new ReagentMolecule("某路径\\某分子.mol", ReagentMolecule.STRUCTURE);
(new ObjectOutputStream(new FileOutputStream("某路径\\某对象.sav"))).writeObject(md);
ReagentMolecule mt=(ReagentMolecule) (new ObjectInputStream(new FileInputStream("某路径\\某对象.sav"))).readObject();
JFrame jf=new JFrame();
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
jf.setBounds(0,0,800,800);
jf.add(mt.getDisplayContainer());
jf.setVisible(true);
System.out.println(mt.str_name);
}
}