Java XML デジタル署名 API は、XML 署名を生成および検証するための標準の Java API です。この API は、Java Community Process のもとで JSR 105 として定義されました (http://jcp.org/en/jsr/detail?id=105
を参照)。この JSR は最終的なものであり、このリリースの Java SE には、API の最終バージョンの FCS アクセス実装が含まれています。
XML 署名は、任意のタイプのデータ (XML またはバイナリ) に適用できます (http://www.w3.org/TR/xmldsig-core/
を参照)。結果の署名は、XML で表されます。XML 署名は、データを保護するために使用でき、データの整合性、メッセージ認証、および署名者認証を提供します。
このドキュメントでは、XML 署名と XML デジタル署名 API の概要を説明したあとで、API を使用して XML 署名を検証および生成する方法を示す 2 つの例について説明します。このドキュメントは、読者に暗号化およびデジタル署名の基本的な知識があることを前提としています。
API は、「XML-Signature Syntax and Processing」での W3C 勧告の必須機能または推奨機能をすべてサポートするように設計されています。API は拡張可能およびプラグイン可能であり、Java 暗号化サービスプロバイダアーキテクチャーに基づいてます。API は、次の 2 種類の開発者向けに設計されています。
次に示す 6 個のパッケージが、XML デジタル署名 API を構成します。
javax.xml.crypto
パッケージには、XML 署名の生成や XML データの暗号化など、XML 暗号化操作を行う場合に使用する共通クラスが含まれています。このパッケージで注意すべき 2 つのクラスのうちの 1 つは KeySelector
クラスで、開発者は、KeyInfo
オブジェクトに含まれる情報を使用して鍵を見つけてオプションで検証する実装を提供できます。もう 1 つのクラスは URIDereferencer
クラスで、開発者は、実装を間接参照する独自の URI を作成および指定できます。
javax.xml.crypto.dsig
パッケージには、W3C XML デジタル署名仕様で定義されているコア要素を表すインタフェースが含まれています。もっとも重要なのは XMLSignature
クラスです。このクラスを使用すると、XML デジタル署名の署名および検証を行うことができます。XML 署名構造または要素のほとんどは、対応するインタフェースによって表されます (KeyInfo
構造を除く。これは独自のパッケージに含まれており、次の段落で説明)。これらのインタフェースには、SignedInfo
、CanonicalizationMethod
、SignatureMethod
、Reference
、Transform
、DigestMethod
、XMLObject
、Manifest
、SignatureProperty
、および SignatureProperties
があります。XMLSignatureFactory
クラスは、これらのインタフェースを実装するオブジェクトを作成する場合に使用する抽象ファクトリです。
javax.xml.crypto.dsig.keyinfo
パッケージには、W3C XML デジタル署名勧告で定義されているほとんどの KeyInfo
構造を表すインタフェースが含まれています。この構造には、KeyInfo
、KeyName
、KeyValue
、X509Data
、X509IssuerSerial
、RetrievalMethod
、および PGPData
があります。KeyInfoFactory
クラスは、これらのインタフェースを実装するオブジェクトを作成する場合に使用する抽象ファクトリです。
javax.xml.crypto.dsig.spec
パッケージには、ダイジェスト、署名、変換、または XML 署名の処理で使用される正規化アルゴリズム用の入力パラメータを表すインタフェースおよびクラスが含まれています。
最後に、javax.xml.crypto.dom
および javax.xml.crypto.dsig.dom
パッケージには、javax.xml.crypto
および javax.xml.crypto.dsig
パッケージ用の DOM 固有のクラスが含まれています。DOM ベースの XMLSignatureFactory
または KeyInfoFactory
実装を作成または使用する開発者およびユーザーのみが、これらのパッケージを直接使用する必要があります。
JSR 105 暗号化サービスは、XMLSignatureFactory
および KeyInfoFactory
抽象クラスの固定実装であり、XML 署名や KeyInfo
構造を解析、生成、および検証するオブジェクトやアルゴリズムを作成します。XMLSignatureFactory
の固定実装は、XML 署名について W3C 勧告で指定されているように、必須アルゴリズムそれぞれをサポートする必要があります。オプションで、W3C 勧告またはその他の仕様で定義されているように、その他のアルゴリズムをサポートできます。
JSR 105 は、JCA プロバイダモデルを使用して XMLSignatureFactory
および KeyInfoFactory
実装の登録とロードを行います。
固定実装 XMLSignatureFactory
または KeyInfoFactory
はそれぞれ、XML 署名や KeyInfo
構造を解析および生成するときに実装によって内部で使用される XML 処理メカニズムを識別する特定の「XML メカニズムタイプ」をサポートしています。この JSR は、1 つの標準形式である DOM をサポートします。Java SE にバンドルされている XML デジタル署名プロバイダ実装は、DOM メカニズムをサポートします。JDOM などの新しい標準形式のサポートが、将来的に追加される可能性があります。
XML デジタル署名 API 実装は、java.security.Signature
や java.security.MessageDigest
など、基盤となる JCA エンジンクラスを使用して暗号化操作を実行するようにしてください。
XMLSignatureFactory
および KeyInfoFactory
クラス以外に、JSR 105 は、変換および正規化アルゴリズム用のサービスプロバイダインタフェースもサポートしています。TransformService
クラスを使用すると、特定の XML メカニズムタイプ用の固有の変換または正規化アルゴリズムの実装を開発およびプラグインすることができます。TransformService
クラスは、実装を登録およびロードするときに標準 JCA プロバイダモデルを使用します。各 JSR 105 実装は、TransformService
クラスを使用して、生成または検証する XML 署名内の変換および正規化アルゴリズムをサポートするプロバイダを見つけるようにしてください。
XML 署名を使用すると、XML かバイナリかにかかわらず、任意のデータに署名できます。データは 1 つ以上の Reference 要素内の URI によって識別されます。XML 署名は分離、内包、または包含の 3 つの形式のうち、1 つ以上の形式で記述されます。分離署名は外部、つまり署名要素自体の外にあるデータに対するものです。内包署名は署名要素の内側にあるデータに対する署名です。包含署名は署名対象のデータ内に含まれる署名です。
XML 署名の内容を説明するもっとも簡単な方法は、実際のサンプルを示し、各コンポーネントについて詳細に説明することです。次に、XML ドキュメントの内容に対して生成される包含 XML 署名の例を示します。署名される前のドキュメントの内容は、次のとおりです。
結果の包含 XML 署名は次のとおりです。読みやすくするために、インデントおよび形式設定されています。
<?xml version="1.0" encoding="UTF-8"?> <Envelope xmlns="urn:envelope"> <Signature xmlns="http://www.w3.org/2000/09/xmldsig#"> <SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/> <Reference URI=""> <Transforms> <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <DigestValue>uooqbWYa5VCqcJCbuymBKqm17vY=</DigestValue> </Reference> </SignedInfo> <SignatureValue> KedJuTob5gtvYx9qM3k3gm7kbLBwVbEQRl26S2tmXjqNND7MRGtoew== </SignatureValue> <KeyInfo> <KeyValue> <DSAKeyValue> <P> /KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxe Eu0ImbzRMqzVDZkVG9xD7nN1kuFw== </P> <Q>li7dzDacuo67Jg7mtqEm2TRuOMU=</Q> <G> Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/ XPaF5Bpsy4pNWMOHCBiNU0NogpsQW5QvnlMpA== </G> <Y> qV38IqrWJG0V/mZQvRVi1OHw9Zj84nDC4jO8P0axi1gb6d+475yhMjSc/ BrIVC58W3ydbkK+Ri4OKbaRZlYeRA== </Y> </DSAKeyValue> </KeyValue> </KeyInfo> </Signature> </Envelope>
Signature
要素は署名対象の内容に挿入されており、それによって包含署名になっています。必須の SignedInfo
要素には、実際に署名される次の情報が含まれています。
<SignedInfo> <CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"/> <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1"/> <Reference URI=""> <Transforms> <Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/> </Transforms> <DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> <DigestValue>uooqbWYa5VCqcJCbuymBKqm17vY=</DigestValue> </Reference> </SignedInfo>
必須の CanonicalizationMethod
要素は、署名または検証の前に SignedInfo
要素を正規化するために使用されるアルゴリズムを定義します。正規化とは、そのデータに対する署名を無効化する可能性がある変更を考慮するために、XML の内容を正規形に変換する処理のことを意味します。正規化は、XML の性質および異なるプロセッサと中間的存在による解析のために必要です。正規化により、署名が無効になっても署名されたデータが論理的に同等であるようにデータを変更できます。
必須の SignatureMethod
要素は、署名の生成に使用されるデジタル署名アルゴリズムを定義します。この場合は、SHA-1 を使用する DSA です。
1 つ以上の Reference
要素が、ダイジェストされるデータを識別します。各 Reference
要素は、URI によってデータを識別します。この例では、URI の値は空の文字列 ("") で、これはドキュメントのルートを示します。オプションの Transforms
要素には、1 つ以上の Transform
要素のリストが含まれており、それぞれがダイジェスト前のデータの変換に使用される変換アルゴリズムを記述します。この例では、包含変換アルゴリズムの Transform
要素が 1 つあります。包含変換は、署名値を計算する前に署名要素自体が削除されるようにするために、包含署名に必要です。必須の DigestMethod
要素は、データのダイジェストに使用されるアルゴリズムを定義します。この場合は、SHA1 です。最後に、必須の DigestValue
要素には、実際の Base64 でエンコードされたダイジェスト値が含まれています。
必須の SignatureValue
要素には、SignedInfo
要素に対する署名の Base64 でエンコードされた署名値が含まれています。
オプションの KeyInfo
要素には、署名の検証に必要な鍵に関する情報が含まれています。
<KeyInfo> <KeyValue> <DSAKeyValue> <P> /KaCzo4Syrom78z3EQ5SbbB4sF7ey80etKII864WF64B81uRpH5t9jQTxe Eu0ImbzRMqzVDZkVG9xD7nN1kuFw== </P> <Q>li7dzDacuo67Jg7mtqEm2TRuOMU=</Q> <G> Z4Rxsnqc9E7pGknFFH2xqaryRPBaQ01khpMdLRQnG541Awtx/ XPaF5Bpsy4pNWMOHCBiNU0NogpsQW5QvnlMpA== </G> <Y> qV38IqrWJG0V/mZQvRVi1OHw9Zj84nDC4jO8P0axi1gb6d+475yhMjSc/ BrIVC58W3ydbkK+Ri4OKbaRZlYeRA== </Y> </DSAKeyValue> </KeyValue> </KeyInfo>
この KeyInfo
要素には KeyValue
要素が含まれています。この要素には、署名の検証に必要な公開鍵で構成される DSAKeyValue
要素が含まれています。KeyInfo
には、X.509 証明書および PGP 鍵識別子など、さまざまな内容を含めることができます。さまざまなKeyInfo
タイプの詳細については、XML 署名勧告の KeyInfo
セクションを参照してください。
次のセクションでは、XML デジタル署名 API を使用する方法を示す 2 つの例について説明します。
このセクションで示すコードは、docs/technotes/guides/security/xmldsig
ディレクトリの Validate.java
ファイルにあります。処理のもとになるファイル envelopedSignature.xml
は、同じディレクトリ内にあります。
この例をコンパイルして実行するには、docs/technotes/guides/security/xmldsig
ディレクトリから次のコマンドを実行します。
$ javac Validate.java
$ java Validate signature.xml
signature.xml
の署名を検証します。
この例では、JSR 105 API を使用して XML 署名を検証する方法を示します。この例は DOM (Document Object Model) を使用して、署名要素を含む XMLドキュメントおよび JSR 105 DOM 実装を解析し、署名を検証します。
最初に、JAXP DocumentBuilderFactory
を使用して、署名を含む XML ドキュメントを解析します。アプリケーションは、次のコード行を呼び出すことによって、DocumentBuilderFactory
のデフォルト実装を取得します。
ファクトリ名前空間認識も作成します。
次に、このファクトリを使用して DocumentBuilder
のインスタンスを取得します。これはドキュメントの解析で使用されます。
DocumentBuilder builder = dbf.newDocumentBuilder(); Document doc = builder.parse(new FileInputStream(argv[0]));
ドキュメントに複数含まれている場合もあるため、検証する Signature
要素を指定する必要があります。DOM メソッド Document.getElementsByTagNameNS
を使用して、次に示すように、Signature
要素の XML 署名名前空間 URI およびタグ名をメソッドに渡します。
NodeList nl = doc.getElementsByTagNameNS (XMLSignature.XMLNS, "Signature"); if (nl.getLength() == 0) { throw new Exception("Cannot find Signature element"); }
これは、ドキュメント内にあるすべての Signature
要素のリストを返します。この例では Signature
要素は 1 つのみです。
署名を検証するための入力パラメータを含む XMLValidateContext
インスタンスを作成します。DOM を使用しているため、DOMValidateContext
インスタンス (XMLValidateContext
のサブクラス) をインスタンス化し、2 つのパラメータを渡します。2 つのパラメータとは、KeyValueKeySelector
オブジェクトと、先に生成した NodeList
の最初のエントリである、検証する Signature
要素の参照です。
KeyValueKeySelector
については、「KeySelector の使用」で詳細に説明します。
Signature
要素の内容を XMLSignature
オブジェクトに抽出します。この処理は非整列化と呼ばれます。Signature
要素は XMLSignatureFactory
オブジェクトを使用して非整列化されます。アプリケーションは次のコード行を呼び出すことによって、XMLSignatureFactory
の DOM 実装を取得できます。
次に、ファクトリの unmarshalXMLSignature
メソッドを呼び出して XMLSignature
オブジェクトを非整列化し、先に作成した検証コンテキストを渡します。
これで、署名を検証する準備が整いました。XMLSignature
オブジェクトで validate
メソッドを呼び出して、次に示すように検証コンテキストを渡します。
W3C XML 署名勧告
のコア検証規則
に従って署名が正常に検証されると、validate
メソッドは「true」を返します。それ以外の場合は false を返します。
XMLSignature.validate
メソッドが false を返した場合、失敗の原因の絞り込みを試行できます。コア XML 署名検証には、次の 2 つのフェーズがあります。
署名が有効になるには、それぞれのフェーズが成功する必要があります。署名の暗号化の検証が失敗したかどうかをチェックするために、次に示すようにステータスをチェックできます。
boolean sv = signature.getSignatureValue().validate(valContext); System.out.println("signature validation status: " + sv);
次に示すように、参照に対して繰り返し実行して、各参照の検証ステータスをチェックすることもできます。
Iterator i = signature.getSignedInfo().getReferences().iterator(); for (int j=0; i.hasNext(); j++) { boolean refValid = ((Reference) i.next()).validate(valContext); System.out.println("ref["+j+"] validity status: " + refValid); }
KeySelectors
は、XMLSignature の検証に必要な鍵を見つけて選択するために使用されます。以前は、DOMValidateContext
オブジェクトを作成したときは、KeySelector
オブジェクトを最初の引数として渡しました。
また、署名の検証に必要な鍵がすでにわかっている場合は、PublicKey
を最初の引数として渡すこともできました。ただし、多くの場合はわかりません。
KeyValueKeySelector
は、抽象 KeySelector
クラスの固定実装です。KeyValueKeySelector
実装は、XMLSignature
の KeyInfo
要素の KeyValue
要素に含まれるデータを使用して、適切な検証鍵を見つけようとします。鍵が信頼できるかどうかは判断しません。これは、単純な KeySelector
実装であり、実際の使用というよりは説明のために設計されています。KeySelector
のより実用的な例は、KeyInfo
に含まれる X509Data
情報 (X509SubjectName
要素、X509IssuerSerial
要素、X509SKI
要素、X509Certificate
要素など) に一致する信頼できる鍵で KeyStore
を検索する例です。
KeyValueKeySelector
の実装は、次のとおりです。
private static class KeyValueKeySelector extends KeySelector { public KeySelectorResult select(KeyInfo keyInfo, KeySelector.Purpose purpose, AlgorithmMethod method, XMLCryptoContext context) throws KeySelectorException { if (keyInfo == null) { throw new KeySelectorException("Null KeyInfo object!"); } SignatureMethod sm = (SignatureMethod) method; List list = keyInfo.getContent(); for (int i = 0; i < list.size(); i++) { XMLStructure xmlStructure = (XMLStructure) list.get(i); if (xmlStructure instanceof KeyValue) { PublicKey pk = null; try { pk = ((KeyValue)xmlStructure).getPublicKey(); } catch (KeyException ke) { throw new KeySelectorException(ke); } // make sure algorithm is compatible with method if (algEquals(sm.getAlgorithm(), pk.getAlgorithm())) { return new SimpleKeySelectorResult(pk); } } } throw new KeySelectorException("No KeyValue element found!"); } static boolean algEquals(String algURI, String algName) { if (algName.equalsIgnoreCase("DSA") && algURI.equalsIgnoreCase(SignatureMethod.DSA_SHA1)) { return true; } else if (algName.equalsIgnoreCase("RSA") && algURI.equalsIgnoreCase(SignatureMethod.RSA_SHA1)) { return true; } else { return false; } } }
このセクションで説明するコードは、docs/technotes/guides/security/xmldsig
ディレクトリの GenEnveloped.java
ファイルにあります。処理のもとになるファイル envelope.xml
は、同じディレクトリ内にあります。このファイルは、ファイル envelopedSignature.xml
を生成します。
このサンプルをコンパイルして実行するには、docs/technotes/guides/security/xmldsig
ディレクトリから次のコマンドを実行します。
$ javac GenEnveloped.java
$ java GenEnveloped envelope.xml envelopedSignature.xml
サンプルプログラムはファイル envelope.xml 内のドキュメントの包含署名を生成し、現在の作業ディレクトリ内のファイル envelopedSignature.xml
に保存します。
この例では、XML デジタル署名 API を使用して XML 署名を生成する方法を示します。より具体的には、この例は XML ドキュメントの包含 XML 署名を生成します。包含署名とは、署名対象の内容に含まれる署名です。この例は DOM (Document Object Model) を使用して、署名対象の XMLドキュメントおよび JSR 105 DOM 実装を解析し、結果の署名を生成します。
XML 署名およびそのさまざまなコンポーネントの基本的な知識が、このセクションの理解に役立ちます。詳細は、http://www.w3.org/TR/xmldsig-core/
を参照してください。
最初に、JAXP DocumentBuilderFactory
を使用して、署名する XML ドキュメントを解析します。アプリケーションは、次のコード行を呼び出すことによって、DocumentBuilderFactory
のデフォルト実装を取得します。
ファクトリ名前空間認識も作成します。
次に、このファクトリを使用して DocumentBuilder
のインスタンスを取得します。これはドキュメントの解析で使用されます。
DocumentBuilder builder = dbf.newDocumentBuilder(); Document doc = builder.parse(new FileInputStream(argv[0]));
公開鍵のペアを生成します。あとの例では、非公開鍵を使用して署名を生成します。KeyPairGenerator
を使用して、鍵のペアを作成します。この例では、512 バイトの長さの DSA KeyPair
を作成します。
KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA"); kpg.initialize(512); KeyPair kp = kpg.generateKeyPair();
実際には、非公開鍵は通常は以前に生成されて、関連する公開鍵証明書とともに KeyStore
ファイルに保存されています。
署名を生成するための入力パラメータを含む、XML デジタル署名 XMLSignContext
を作成します。DOM を使用しているので、DOMSignContext
(XMLSignContext
のサブクラス) をインスタンス化し、2 つのパラメータを渡します。パラメータは、ドキュメントの署名に使用される非公開鍵と署名対象のドキュメントのルートです。
Signature
要素のさまざまな部分を組み合わせて、XMLSignature
オブジェクトにします。これらのオブジェクトはすべて、XMLSignatureFactory
オブジェクトを使用して、作成され組み合わせられます。アプリケーションは次のコード行を呼び出すことによって、XMLSignatureFactory
の DOM 実装を取得します。
次に示すように、さまざまなファクトリメソッドを呼び出して XMLSignature
オブジェクトのさまざまな部分を作成します。Reference
オブジェクトを作成して、そのオブジェクトに次のものを渡します。
次に、SignedInfo
オブジェクトを作成します。これは、次に示すように、実際に署名されるオブジェクトです。SignedInfo
を作成するときは、次のものをパラメータとして渡します。
次に、オプションの KeyInfo
オブジェクトを作成します。このオブジェクトには、受け側が署名の検証に必要な鍵を見つけられるようにする情報が含まれています。この例では、公開鍵を含む KeyValue
オブジェクトを追加します。KeyInfo
およびそのさまざまなサブタイプを作成するには、KeyInfoFactory
オブジェクトを使用します。このオブジェクトは次に示すように、XMLSignatureFactory
の getKeyInfoFactory
メソッドを呼び出すことによって取得できます。
次に、KeyInfoFactory
を使用して KeyValue
オブジェクトを作成し、KeyInfo
オブジェクトに追加します。
KeyValue kv = kif.newKeyValue(kp.getPublic()); KeyInfo ki = kif.newKeyInfo(Collections.singletonList(kv));
最後に、XMLSignature
オブジェクトを作成し、先に作成した SignedInfo
および KeyInfo
オブジェクトをパラメータとして渡します。
まだ実際には署名を生成していません。次のステップで生成します。
これで、署名を生成する準備が整いました。XMLSignature
オブジェクトで sign
メソッドを呼び出して、次に示すように署名コンテキストを渡します。
これで、結果のドキュメントには署名が含まれています。署名は、ルート要素の最後の子要素として挿入されました。
次のコードを使用して、結果の署名済みドキュメントをファイルまたは標準出力に印刷できます。
OutputStream os; if (args.length > 1) { os = new FileOutputStream(args[1]); } else { os = System.out; } TransformerFactory tf = TransformerFactory.newInstance(); Transformer trans = tf.newTransformer(); trans.transform(new DOMSource(doc), new StreamResult(os));