原则4.1 敏感对象发送出信任区域前进行签名并加密
说明:敏感数据传输过程中要防止窃取和非法篡改。使用安全的加密算法给数据加密可以防止数据被窃取。而对数据加上数字签名则可以防止数据被非法篡改。在以下场景中,需要加密和数字签名的机制保证数据安全:
1)序列化或传输敏感数据;2)无SSL传输通道或者代价太高;3)敏感数据需要长久保存;应该要避免使用私有加密算法,以免引入更多的漏洞。应用程序在readObject()和writeObject()方法中使用私有加密算法是典型的反例。本规则的错误示例代码和推荐做法都是基于下面的代码来说明:
class SerializableMap<K, V> implements Serializable { final static long serialVersionUID = -2648720192864531932L; private Map<K, V> map; public SerializableMap() { map = new HashMap<K, V>(); } public Object getData(K key) { return map.get(key); } public void setData(K key, V data) { map.put(key, data); } } public class MapSerializer { public static SerializableMap<String, Integer> buildMap() { SerializableMap<String, Integer> map = new SerializableMap<String, Integer>(); map.setData("John Doe", new Integer(123456789)); map.setData("Richard Roe", new Integer(246813579)); return map; } public static void InspectMap(SerializableMap<String, Integer> map) { System.out.println("John Doe's number is " + map.getData("John Doe")); System.out.println("Richard Roe's number is " + map.getData("Richard Roe")); } public static void main(String[] args) { // ... } } 错误示例1:未做任何安全防护 public static void main(String[] args) throws IOException, ClassNotFoundException { // Build map SerializableMap<String, Integer> map = buildMap(); // Serialize map ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); out.writeObject(map); out.close(); // Deserialize map ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); map = (SerializableMap<String, Integer>) in.readObject(); in.close(); // Inspect map InspectMap(map); } 错误示例2:只加密 public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException { // Build map SerializableMap<String, Integer> map = buildMap(); // Generate sealing key & seal map KeyGenerator generator; generator = KeyGenerator.getInstance("AES"); generator.init(new SecureRandom()); Key key = generator.generateKey(); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, key); SealedObject sealedMap = new SealedObject(map, cipher); // Serialize map ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); out.writeObject(sealedMap); out.close(); // Deserialize map ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); sealedMap = (SealedObject) in.readObject(); in.close(); // Unseal map cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); map = (SerializableMap<String, Integer>) sealedMap.getObject(cipher); // Inspect map InspectMap(map); } 错误示例3:先加密后签名 public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException { // Build map SerializableMap<String, Integer> map = buildMap(); // Generate sealing key & seal map KeyGenerator generator; generator = KeyGenerator.getInstance("AES"); generator.init(new SecureRandom()); Key key = generator.generateKey(); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, key); SealedObject sealedMap = new SealedObject(map, cipher); // Generate signing public/private key pair & sign map KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); KeyPair kp = kpg.generateKeyPair(); Signature sig = Signature.getInstance("SHA1withDSA"); SignedObject signedMap = new SignedObject(sealedMap, kp.getPrivate(), sig); // Serialize map ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); out.writeObject(signedMap); out.close(); // Deserialize map ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); signedMap = (SignedObject) in.readObject(); in.close(); // Verify signature and retrieve map if (!signedMap.verify(kp.getPublic(), sig)) { throw new GeneralSecurityException("Map failed verification"); } sealedMap = (SealedObject) signedMap.getObject(); // Unseal map cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); map = (SerializableMap<String, Integer>) sealedMap.getObject(cipher); // Inspect map InspectMap(map); } 这段代码先对数据进行加密,然后再对加密后的数据进行签名。这样做无法保证签名来自数据的原始来源。 任何恶意的第三方可以截获原始加密签名后的数据,剔除原始的签名,并对密封的数据加上自己的签名。 这样的话,即使恶意第三方无法获取原始的数据内容,正常的接收者也无法得到原始的数据。 推荐做法:先签名后加密 public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException { // Build map SerializableMap<String, Integer> map = buildMap(); // Generate signing public/private key pair & sign map KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); KeyPair kp = kpg.generateKeyPair(); Signature sig = Signature.getInstance("SHA1withDSA"); SignedObject signedMap = new SignedObject(map, kp.getPrivate(), sig); // Generate sealing key & seal map KeyGenerator generator; generator = KeyGenerator.getInstance("AES"); generator.init(new SecureRandom()); Key key = generator.generateKey(); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.ENCRYPT_MODE, key); SealedObject sealedMap = new SealedObject(signedMap, cipher); // Serialize map ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream( "data")); out.writeObject(sealedMap); out.close(); // Deserialize map ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); sealedMap = (SealedObject) in.readObject(); in.close(); // Unseal map cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); signedMap = (SignedObject) sealedMap.getObject(cipher); // Verify signature and retrieve map if (!signedMap.verify(kp.getPublic(), sig)) { throw new GeneralSecurityException("Map failed verification"); } map = (SerializableMap<String, Integer>) signedMap.getObject(); // Inspect map InspectMap(map); } 先签名后加密,既能保证数据的真实可靠性,又能防止“中间人攻击”(man-in-middle attacks)。