センシティブなデータは盗聴や改ざんから保護しなくてはならない。強力な暗号アルゴリズムにより難読化することで、転送するデータを保護することができる。このようなアプローチは、オブジェクトの「シーリング(sealing)」と呼ばれることがある。オブジェクトの完全性(integrity)を保証するには、暗号化したオブジェクトに電子署名を付け加えるとよい。
以下に挙げる場合には、データを保護するために暗号化と署名を行うのがよい。
- センシティブなデータをシリアライズする、あるいはネットワークを介して転送する場合
- SSL(Secure Socket Layer)等の安全な通信チャネルを利用できない、あるいはコストがかかり過ぎる場合
- センシティブなデータが(たとえばハードディスクに)長期間に渡って保存される場合
自分で開発した暗号アルゴリズムは使用しないこと。脆弱性を作り込んでしまうのがおちである。自前の暗号化メカニズムをreadObject()メソッドやwriteObject()メソッドで用いるのは、典型的な「アンチパターン」である。
このルールはセンシティブな情報を意図的にシリアライズする場合に適用される。「SER03-J. 暗号化されていないセンシティブなデータをシリアライズしない」は、センシティブな情報の意図しないシリアライズを防ぐことを目的としたものである。
違反コード
このルールのコード例はすべて、以下に示すコードに基づく。
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) { // ... } }
このコード例は、シリアライズ可能なマップを定義するとともに、マップに値を割り当てるメソッドと、マップに割り当られている値を調べるメソッドを提供している。
以下の違反コード例は単に、マップをシリアライズし、復元している。マップはシリアライズされ、異なる取引き先へと転送することが可能である。残念ながら、このコード例では、バイナリデータが転送されている最中にバイトストリームが改ざんされることを防ぐ手だてが存在しない。同様に、シリアライズされたストリームデータを解析され、HashMap中のデータを復元される可能性がある。
public static void main(String[] args) throws IOException, ClassNotFoundException { // map を構築 SerializableMap<String, Integer> map = buildMap(); // map をシリアライズ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); out.writeObject(map); out.close(); // map を復元 ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); map = (SerializableMap<String, Integer>) in.readObject(); in.close(); // map を検証 InspectMap(map); }
マップ中にセンシティブなデータが保存されている場合、このコードは「SER03-J. 暗号化されていないセンシティブなデータをシリアライズしない」にも違反することになるだろう。
違反コード (暗号化)
以下の違反コード例では、メッセージの機密性を確保するためにjavax.crypto.SealedObjectを使用している。このクラスは、シリアライズしたオブジェクトを、カプセル化し暗号化する。Cipherオブジェクトは、安全な暗号化鍵とパディング方式を使用する強力な暗号アルゴリズムを使って初期化しなくてはならない。seal()メソッドおよびunseal()メソッドはそれぞれ、暗号化および復号化の機能を提供する。
以下の違反コード例では、マップを暗号化してSealedObjectを作成し、たとえ盗聴されたとしてもデータにアクセスできないようにしている。しかし、このプログラムはデータを署名しておらず、データの正当性を確認することはできない。
public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException { // マップを作成 SerializableMap<String, Integer> map = buildMap(); // 鍵を生成し、マップをシールする 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); // マップをシリアライズ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); out.writeObject(sealedMap); out.close(); // マップを復元 ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); sealedMap = (SealedObject) in.readObject(); in.close(); // マップを復号 cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); map = (SerializableMap<String, Integer>) sealedMap.getObject(cipher); // マップを検証 InspectMap(map); }
違反コード (暗号化してから署名)
以下の違反コード例では、オブジェクトの完全性を確保しなくてはならない場合、java.security.SignedObjectクラスを使ってオブジェクトに署名している。SignedObject()に渡される2つの引数は、PublicKeyとKeyPairオブジェクトから取得した秘密鍵である。
以下のコードでは、オブジェクトを暗号化するだけでなく、署名も行っている。Abadi と Needham は以下のように述べている。[Abadi 1996]
ある人が、既に暗号化されているデータに署名する場合、その人がメッセージの内容を知っていると推論することはできない。逆に、メッセージに署名してから暗号化する場合、署名した人がメッセージの内容を知っていると推論することができる。
悪意ある第三者は、暗号化されてから署名されたメッセージを傍受し、署名をはぎ取って、自分の署名をメッセージにつけることができる。暗号化されてから署名されているため、悪意ある第三者もメッセージを受信する人も、元のメッセージの内容についてはまったく知らない(署名を検証して初めてメッセージを複号化できる)。メッセージを受信する人は、本物の送信者の公開鍵を別途安全な通信経路から取得しない限り、送信者の身元を確認するすべがない。CCITT(国際電信電話諮問委員会)のX.509標準プロトコルのひとつでは、このような攻撃が可能であった。[CCITT 1988]
暗号化した後で署名しているため、署名した人が本当にオブジェクを作成したと考えることはできない。
public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException { // マップを作成 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); // 署名のための公開鍵/秘密鍵のペアを生成し、マップに署名する KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); KeyPair kp = kpg.generateKeyPair(); Signature sig = Signature.getInstance("SHA1withDSA"); SignedObject signedMap = new SignedObject(sealedMap, kp.getPrivate(), sig); // マップをシリアライズ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); out.writeObject(signedMap); out.close(); // マップを復元 ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); signedMap = (SignedObject) in.readObject(); in.close(); // 署名を検証し、マップを取り出す if (!signedMap.verify(kp.getPublic(), sig)) { throw new GeneralSecurityException("Map failed verification"); } sealedMap = (SealedObject) signedMap.getObject(); // マップを復号 cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); map = (SerializableMap<String, Integer>) sealedMap.getObject(cipher); // マップを検証 InspectMap(map); }
適合コード (署名してから暗号化)
以下の適合コードでは、オブジェクトを暗号化する前に正しく署名している。こうすることで、中間者攻撃(man-in-the-middle attack)を防ぐだけでなく、オブジェクトが確かに本物であることを示している。
public static void main(String[] args) throws IOException, GeneralSecurityException, ClassNotFoundException { // マップを作成 SerializableMap<String, Integer> map = buildMap(); // 署名のための公開鍵/秘密鍵ペアを生成し、マップに署名 KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); KeyPair kp = kpg.generateKeyPair(); Signature sig = Signature.getInstance("SHA1withDSA"); SignedObject signedMap = new SignedObject(map, kp.getPrivate(), sig); // 暗号化鍵を生成し、マップを暗号化 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); // マップをシリアライズ ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data")); out.writeObject(sealedMap); out.close(); // マップを復元 ObjectInputStream in = new ObjectInputStream(new FileInputStream("data")); sealedMap = (SealedObject) in.readObject(); in.close(); // マップを復号 cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); signedMap = (SignedObject) sealedMap.getObject(cipher); // 署名を検証し、マップを取り出す if (!signedMap.verify(kp.getPublic(), sig)) { throw new GeneralSecurityException("Map failed verification"); } map = (SerializableMap<String, Integer>) signedMap.getObject(); // マップを検証 InspectMap(map); }
例外
SER02-EX0: 暗号化されたオブジェクトに対する署名は、どこか他所から受け取った、暗号化されたオブジェクトの信頼性を保証する目的には使用できる。これは、暗号化されたオブジェクトの内容ではなく、「暗号化されたオブジェクト自身」に対するコミットメントを表す。
SER02-EX1: 署名と暗号化が必要なのは、信頼境界を越えてオブジェクトをやりとりする場合だけである。信頼境界を越えることのないオブジェクトを署名したり暗号化したりする必要はない。たとえば、ネットワーク全体がある信頼境界の中に収まっている場合、ネットワークの外に出ることのないオブジェクトは署名や暗号化の必要はない。別の例として、署名されたバイナリストリームのみに送信されるオブジェクトも署名や暗号化の必要はない。
リスク評価
オブジェクトを署名してから暗号化しないと、オブジェクトの完全性や機密性が失われる恐れがある。
ルール | 深刻度 | 可能性 | 修正コスト | 優先度 | レベル |
---|---|---|---|---|---|
SER02-J | 中 | 中 | 高 | P4 | L3 |
自動検出
一般には、静的解析によってこのルールへの違反を自動検出することはできない。
関連ガイドライン
MITRE CWE | CWE-319. Cleartext transmission of sensitive information |
参考文献
[API 2006] | |
[Gong 2003] | 9.10, Sealing Objects |
[Harold 1999] | Chapter 11, Object serialization, sealed objects |
[Neward 2004] | Item 64, Use SignedObject to provide integrity of serialized objects |
Item 65, Use SealedObject to provide confidentiality of serializable objects | |
[Steel 2005] | Chapter 10, Securing the Business Tier, Obfuscated Transfer Object |
翻訳元
これは以下のページを翻訳したものです。
SER02-J. Sign then seal sensitive objects before sending them outside a trust boundary (revision 71)