原则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)。