XML デジタル署名入門

Rich Salz
DataPower Technology

July 2003

適用対象:

   Web サービス仕様 (WS-Security 仕様、およびその他)
   Microsoft® .NET Framework

要約: 本書では、XML デジタル署名の仕様を考察しながら、その処理モデルといくつかの機能について解説します。WS-Security 仕様のメッセージ セキュリティ機能を実装する方法について、より詳細な下位レベルの知識を提供します。

目次

はじめに
難しい計算のないデジタル署名暗号化
署名の書式
まとめ

はじめに

デジタル署名は重要な機能です。というのも、エンド ツー エンドのメッセージの整合性を保証するだけでなく、メッセージの発信者の認証情報を提供することもできるからです。最大の効果をあげるには、アプリケーション データに署名を組み込み、メッセージが作成されるときに署名が生成され、メッセージが最終的に利用または処理されたときに署名が検証されるようにする必要があります。

SSL/TLS もメッセージの整合性 (およびメッセージのプライバシー) を提供しますが、これはメッセージの送信中に限られます。サーバー (より一般的に言えばピア レシーバ) がメッセージを受信したら、メッセージを処理できるように SSL 保護を "取り除く" 必要があります。

さらに微妙なのは、SSL が通信エンドポイント間でしか機能しないという点です。従来の HTTP サーバー (IIS や Apache) をゲートウェイとして使用して新しい Web サービスを開発している場合や、SSL アクセラレータを使用する大企業と通信している場合は、メッセージの整合性が良好に保たれるのは SSL 接続が終了するまでです。

通常の封書にたとえて考えてみましょう。電話会社に小切手を送る場合、小切手 (メッセージ) に署名をして、プライバシーの保護と配達のために封筒に入れます。その郵便物を受け取った電話会社は、封筒から小切手を取り出し、封筒を捨て、小切手を処理します。はがきに支払金を貼り付けて郵送するなど、メッセージを封筒の一部にすることはできますが、賢明ではありません。

XML 署名では、XML ドキュメントに埋め込むか、別の方法で XML ドキュメントに関連付けることができる一連の XML 要素を定義します。これによって、受信者は、メッセージが変更されておらず、送信者が意図したとおりのものであることを確認できます。

XML-Signature Syntax and Processing 仕様 (本書では XML DSIG と略記します) は、W3C と IETF の共同の取り組みの成果です。2002 年 2 月から正式な W3C 勧告となっており、多くの実装があります。.NET Framework では、System.Security.Cryptography.Xml がこれを実装しています。WS-Security の 3 本柱 (認証、内容の整合性、内容のプライバシー) のうち、XML DSIG は整合性を提供し、送信者の認証の提供に使用できます。

難しい計算のないデジタル署名暗号化

XML DSIG を本当に理解するには、暗号化の基本知識が必要です。ここではその概念を扱いますが、恐れることはありません。複雑な計算は含まれていませんから。

デジタル署名によって、内容に対する整合性チェックが提供されます。価格にゼロが 1 つ追加されている、"2" が "4" に変更さている、"いいえ" が "はい" に変更されているなど、シングルバイトの元の内容が変更されている場合、署名の検証は失敗します。以下でそのしくみを説明します。

最初のステップは、メッセージを "ハッシュ" することです。暗号ハッシュは、任意のバイト ストリームを取り出し、"ダイジェスト" と呼ばれる 1 つの固定長の値に変換します。ダイジェストは、一方向のプロセスです。つまり、ハッシュからメッセージを作成し直したり、ダイジェスト値が同じになる 2 つの異なるメッセージを検出したりすることは "計算的に実行不可能" です。

最も一般的なハッシュ メカニズムは、SHA1 (Secure Hash Algorithm) です。このアルゴリズムは、アメリカ政府によって開発され、1995 年に標準として公開されました。完全な仕様は、http://www.itl.nist.gov/fipspubs/fip180-1.htm から入手できます。SHA1 は、最大 2**64 バイト長のメッセージを取り出し、20 バイトの結果を生成します (したがって、2**160 とおりのダイジェスト値が可能であるということです。現在、宇宙に存在する陽子の数は約 2**250 と考えられていることと比較してみてください)。

たとえば、私がメッセージ M を作成して、ダイジェストを作成し ("M のハッシュ" を "H(M)" と記述します)、あなたがその "M" と "H(M)" を受信した場合、あなたはダイジェスト "H'(M)" を作成できます。そして、その 2 つのダイジェスト値が一致した場合、私が送信したものをあなたが受け取ったということがお互いにわかります。"M" を変更されないように保護するには、"H(M)" を変更されないように保護するだけで済みます。

では、それは、どのように行うのでしょう。一般的な方法は 2 つあります。1 つは、共有シークレットをダイジェストに組み込むという方法です。つまり、"H(S+M)" を作成します。メッセージを受け取ったときに、自分の "S" のコピーを使用して "H'(S+M)" を作成します。この新しいダイジェストは、HMAC (Hashed Messsage Authentication Code) と呼ばれます。

HMAC を使用する場合、整合性保護の強度は攻撃者が S を見つけ出すことができるかどうかに応じて異なります。したがって、S は簡単には推量できず、また頻繁に変更されるものにする必要があります。これらの必要条件を満たす最善の方法の 1 つとして、Kerberos の使用があります。Kerberos では、2 つのエンティティが通信しようとするときに、中央機関が一時的なセッション キーを含む "チケット" を配布します。このセッション キーは、共有シークレットとして使用されます。たとえば、私があなたに署名を送るときに、私はあなたと話すためのチケットを受け取ります。私は、自分の分のチケットを開いて S を取得し、メッセージ、メッセージの HMAC およびあなたの分のチケットをあなたに送信します。あなたはチケットを開き (最初に Kerberos に登録したパスワードを使用して)、S と私の身元に関する情報を取得します。これで、あなたはメッセージ M を取り出して、自分の H'(S+M) を生成し、それらが一致しているかどうかを確認できます。それらが一致していたら、変更されていないそのままの私のメッセージを受け取ったことがわかります。また、Kerberos は私の身元をあなたに知らせます。

ダイジェストを保護するもう 1 つの方法は、RSA などの公開キー暗号化を使用することです。公開キー暗号化には、秘密キー (所有者だけが知っている) と公開キー (キーの所有者と通信したい人は誰でもアクセスできる) の 2 つのキーがあります。公開キー暗号化では、秘密キーを使用して暗号化されたものはすべて公開キーを使用して解読できます。また、逆の場合も同様です。

公開キー暗号化のしくみを示す簡単な例を見てみましょう。この例では、メッセージを a から z までの文字に制限し、それらの文字に 1 から 26 までの値を割り当てています。暗号化するには、秘密キーの値を加算します。この場合の秘密キーの値は +4 です。

文字 h e l l o
数値 8 5 12 12 15
秘密キー 4 4 4 4 4
暗号化された値 12 9 16 16 19

解読するには、公開キーを加算します。この場合の公開キーは +22 です。結果が数字の範囲を超える場合は、有効な値になるまで 26 を加減します (つまり、解読するには公開キーを加算して結果を 26 で除算した余りをとります)。

暗号化された値 12 9 16 16 19
公開キー 22 22 22 22 22
未処理の暗号化された値 34 31 38 38 41
正規化された値 8 5 12 12 15
プレーンテキスト h e l l o

RSA も同様のしくみですが、加算の代わりに累乗法を使用し、数が数百桁の長さになる点が違います。

たとえば、私が RSA を使用してダイジェスト "H(M)" を作成し、私の秘密キーである "{H(M)}秘密キー" で暗号化したとします。これが署名になります。あなたはメッセージ "M" を受け取ると、ダイジェスト "H'(M)" を作成し、私の公開キーを使用して署名を解読し、私が作成した "H(M)" を取得します。"H(M)" と "H'(M)" が同じであれば、"M" が同じであることがわかります。さらに、あなたは、秘密キーの所有者 (つまり、私) がメッセージの送信者であることもわかります。

署名の書式

XML-DSIG では単一の名前空間が使用されるので、この例では以下の宣言があることを前提としています。

    xmlns:ds="http://www.w3.org/2000/09/xmldsig#"

最上位レベルの <ds:Signature> 要素はかなり単純です。この要素には、署名対象、署名、その署名の作成に使用されたキー、任意の情報を保存する場所についての情報が格納されます。

    <element name="Signature" type="ds:SignatureType"/>
    <complexType name="SignatureType">
      <sequence> 
        <element ref="ds:SignedInfo"/> 
        <element ref="ds:SignatureValue"/> 
        <element ref="ds:KeyInfo" minOccurs="0"/> 
        <element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/> 
      </sequence>  
      <attribute name="Id" type="ID" use="optional"/>
    </complexType>

これらの情報を以下のように複雑さの度合いが小さい順に見ていきます。

     Id
     ds:SignatureValue
     ds:Object
     ds:SignedInfo
     ds:KeyInfo

ds:Signature/@Id 属性

グローバル Id 属性によって、複数の署名が含まれたドキュメントを許可し、特定のインスタンスを識別する方法が提供されます。ビジネス ポリシーでは、管理者と旅行会社の両方が出張申請を承認する場合などのように、複数の署名を使用するのは一般的です。

ds:SignatureValue 要素

この要素には、実際の署名が格納されます。署名は必ずバイナリ データなので、XML DSIG では署名値は Base64 方式でエンコードされた内容を持つ単純な要素であると規定されています。

    <element name="SignatureValue" type="ds:SignatureValueType"/> 
    <complexType name="SignatureValueType">
      <simpleContent>
        <extension base="base64Binary">
          <attribute name="Id" type="ID" use="optional"/>
        </extension>
      </simpleContent>
    </complexType>

SignatureValue を解釈するには、後で説明する SignedInfo 要素の内容を把握する必要があります。それまでは、この要素は不可解なバイト列にすぎません。

    <SignatureValue>
        WvZUJAJ/3QNqzQvwne2vvy7U5Pck8ZZ5UTa6pIwR7GE+PoGi6A1kyw==
    </SignatureValue>

ds:Signature/ds:Object 要素

後で説明するように、XML DSIG は複数のアイテムを保護することができます。通常、Web ページや XML ビジネス ドキュメントなどのように、アイテムは独立で存在できますが、署名された "真の" 内容のメタデータとして処理する方がよい場合もあります。たとえば、データが、署名が生成されたときのタイムスタンプなどの署名の "プロパティ" である場合がそうです。

ds:Object 要素は、Signature 内のデータを保持するために使用できます。

    <element name="Object" type="ds:ObjectType"/> 
    <complexType name="ObjectType" mixed="true">
      <sequence minOccurs="0" maxOccurs="unbounded">
        <any namespace="##any" processContents="lax"/>
      </sequence>
      <attribute name="Id" type="ID" use="optional"/> 
      <attribute name="MimeType" type="string" use="optional"/>
      <attribute name="Encoding" type="anyURI" use="optional"/> 
    </complexType>

Id 属性では、署名が別々にアドレス指定できる複数のオブジェクトを持つことができます。MimeType は、他のプロセッサが使用できるようにデータを識別するために使用されます。したがって、この属性は、DSIG プロセッサにとっては何の意味も持ちません。

Encoding は、内容の前処理の方法を示します。現在は base-64 エンコードだけが定義されています。

ドキュメントが署名された時間を示す単純なインジケータとして使用できる 2 つのオブジェクト (同一の内容) を以下に示します。署名にこれを提供するサービスは、オンラインのコンテストやオークションなどの提出期限がある活動の場合に便利です。

    <ds:Object Id="ts-bin" Encoding="http://www.w3.org/2000/09/xmldsig#base64">
        V2VkIEp1biAgNCAxMjoxMTowMyBFRFQgMjAwMwo
    </ds:Object>
    <ds:Object Id="ts-text">
        Wed Jun  4 12:11:06 EDT
    </ds:Object>

ds:SignedInfo 要素

"コンピュータ サイエンスの問題は、別のレベルの間接的手段によって解決できる" という格言を聞いたことはあるかもしれませんが、 ここでこれから見ていこうとしているように、 XML DSIG はその主な例です。

ds:SignedInfo の内容は、以下の XML スキーマ フラグメントに示すように、SignatureValue に関する情報とアプリケーション コンテンツに関する情報の 2 つの部分に分かれています。

   <element name="SignedInfo" type="ds:SignedInfoType"/> 
   <complexType name="SignedInfoType">
     <sequence> 
       <element ref="ds:CanonicalizationMethod"/>
       <element ref="ds:SignatureMethod"/> 
       <element ref="ds:Reference" maxOccurs="unbounded"/> 
     </sequence>  
     <attribute name="Id" type="ID" use="optional"/> 
   </complexType>

XML の構文はかなり柔軟です。たとえば、属性の順序や値に引用符を付ける方法などはあまり重要ではありません。XML 処理ソフトウェアに関する限り、以下の 2 つの例は完全に等価です。

    <a foo='yes' boo="no"/>
    <a boo="no" foo="yes"  ></a>

(注意深い読者の方は、私が加えたほかの 2 つの違いを探そうとするかもしれません。) しかし、署名はメッセージ ダイジェストを必要とするため、そのような違いは非常に重大です。

これに対処するために、内容を 「正規化 (Canonicalization; C14N)」 する必要があります。正規化、つまり C14N は、可能なすべての出力オプションから 1 つのパスを選定するプロセスです。これによって、どのような中間 XML ソフトウェアが関係していても、送信者と受信者がまったく同じバイト値を生成できます。C14N は、それ自体で 1 つの論文ができるほどの奥深いテーマです。

ds:SignedInfo/ds:CanonicalizationMethod 要素は、正確なバイト ストリームを再構築する方法を示します。ds:SignedInfo/ds:SignatureMethod 要素は、署名の作成に使用した署名の種類 (Kerberos や RSA など) を示します。これらの 2 つの要素が総合してダイジェストの作成方法と変更の防止方法が示されます。

以下に例を示します。

    <ds:SignedInfo>
        <ds:CanonicalizationMethod
             Algorithm="http://www.w3.org/2001/10/xml-exc-c14n"/>
        <ds:SignatureMethod
             Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
        ...

ds:Reference 要素

ds:SignatureValue 要素には、ds:SignedInfo 要素を保護するだけの署名が格納されます。署名ダイジェストには ds:SignedInfo の内容だけが含まれています。それでは、その他の内容には実際にどのようにして署名するのでしょうか。ds:Reference 要素にその秘訣と能力があります。

上記の ds:SignedInfoType のスキーマ定義からわかるように、署名は複数の参照を持つことができます。したがって、1 つの XML DSIG が複数のオブジェクト (MIME メッセージ、XML ファイル、および XML ファイルを HTML に変換する XSLT スクリプトのすべての部分) を保護することが可能になります。

ds:Reference 要素は、ほかの内容を参照します。この要素には、内容のダイジェスト、そのダイジェストが生成された方法 (SHA1 など) の表示、ダイジェストの生成前に内容を変換する方法の指定が格納されます。この変換によって XML DSIG に優れた柔軟性が与えられます。以下はそのスキーマ フラグメントです。

   <element name="Reference" type="ds:ReferenceType"/>
   <complexType name="ReferenceType">
     <sequence> 
       <element ref="ds:Transforms" minOccurs="0"/> 
       <element ref="ds:DigestMethod"/> 
       <element ref="ds:DigestValue"/> 
     </sequence>
     <attribute name="Id" type="ID" use="optional"/> 
     <attribute name="URI" type="anyURI" use="optional"/> 
     <attribute name="Type" type="anyURI" use="optional"/> 
   </complexType>

Type 属性は、処理のヒントを提供しますが、一般的には有効ではありません。

URI は、参照先の実際の内容を指します。この属性は URI であるため、Web の機能をすべて使用できます。たとえば、以下のように MSDN ホーム ページの内容に署名できます。

    <ds:Reference URI="https://msdn.microsoft.com">
        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <ds:DigestValue>HB7i8RaV7ZvuUlaTzZVx0S3POpU=</ds:DigestValue>
    </ds:Reference>

XML ドキュメント内の内容 (前述したタイムスタンプなど) を参照することもできます。

    <ds:Reference URI="#ts-text">
        <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
        <ds:DigestValue>pN3j2OeC0+/kCatpvy1dYfG1g68=</ds:DigestValue>
    </ds:Reference>

さらに、同じ署名の中に両方の参照を入れることもできます。

URI フラグメントは、SOAP メッセージに署名するために WS-Security で最もよく使用されます。

    <SOAP:Envelope xmlns:SOAP="https://schemas.xmlsoap.org/soap/envelope/">
        <SOAP:Header>
           <wsse:Security>
                   xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/07/secext">
               ...
               <ds:Signature>
                    ...
                    <ds:SignedInfo>
                        <ds:Reference URI='#Body'>
                            ...
                        </ds:Reference>
                        ...
                    <ds:SignedInfo>
                    ...
               </ds:Signature>
               ...
           </wsse:Security>
        </SOAP:Header>
        <SOAP:Body Id='Body'>
            ...
        </SOAP:Body>
    </SOAP:Envelope>

ご推察どおり、ds:DigestMethod はハッシュ アルゴリズムを示し、ds:DigestValue は内容のハッシュの Base64 値です。

ds:Reference 要素の最もパワフルな部分は、出現する可能性のある変換のセットです。ds:Transforms は、それぞれが処理ステップを示す ds:Transform 要素の単なる一覧です。スキーマは、一連の変換を定義します。そのうちの 1 つ (ds:XPath) は定義された構造を持ちます。

   <element name="Transforms" type="ds:TransformsType"/>
   <complexType name="TransformsType">
     <sequence>
       <element ref="ds:Transform" maxOccurs="unbounded"/>  
     </sequence>
   </complexType>

   <element name="Transform" type="ds:TransformType"/>
   <complexType name="TransformType" mixed="true">
     <choice minOccurs="0" maxOccurs="unbounded"> 
       <any namespace="##other" processContents="lax"/>
       <element name="XPath" type="string"/> 
     </choice>
     <attribute name="Algorithm" type="anyURI" use="required"/> 
   </complexType>

変換内の内容は、Algorithm 属性によって異なります。たとえば、単純な XML に署名した場合、C14N アルゴリズムを指定する単一の変換がある可能性が最も高くなります。

  <ds:Transforms>
    <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n"/>
  </ds:Transforms>

XML DSIG は、複数の変換を定義します。これらの変換には、すべてのテキストを無視してマークアップだけに署名するなど、ドキュメントの一部に署名することを容易にする XPath 変換も含まれます。

    <ds:Transforms>
        <ds:Transform Algorithm="http://www.w3.org/TR/1999/REC-xpath-19991116">
            <XPath>not(self::text())</XPath>
        </ds:Transform>
        <ds:Transform
                Algorithm="http://www.w3.org/TR/2001/10/xml-exc-c14n/>
        </ds:Transforms>

定義されたそのほかの変換には、埋め込まれた XSLT スタイルシート、解読対象の暗号化されたデータなどが含まれます。

ds:KeyInfo 要素

ここまでで、内容の参照方法、内容の変換とハッシュの方法、その内容を保護する署名の作成方法がわかりました。内容は間接手段を使用して保護されるということを思い出してください。つまり、ds:SignatureValue が ds:SignedInfo を保護し、その ds:SignedInfo には、アプリケーション データのダイジェスト値を含んだ ds:References が含まれているということです。これらのいずれかを変更すると、数学計算の連鎖が切断され、署名は検証されません。

最後に残っているのは、署名者、または少なくとも署名を生成したキー (もっと暗号化に即して表現すれば、ダイジェストを変更から保護するキー) を識別することだけです。それが、以下に示す ds:KeyInfo 要素の仕事です。

   <element name="KeyInfo" type="ds:KeyInfoType"/> 
   <complexType name="KeyInfoType" mixed="true">
     <choice maxOccurs="unbounded">     
       <element ref="ds:KeyName"/> 
       <element ref="ds:KeyValue"/> 
       <element ref="ds:RetrievalMethod"/> 
       <element ref="ds:X509Data"/> 
       <element ref="ds:PGPData"/> 
       <element ref="ds:SPKIData"/>
       <element ref="ds:MgmtData"/>
       <any processContents="lax" namespace="##other"/>
       <!-- (1,1) elements from (0,unbounded) namespaces -->
     </choice>
     <attribute name="Id" type="ID" use="optional"/>
   </complexType>

おわかりのように、XML DSIG はさまざまなキーの種類とキー基盤に対応しており、WS-Security がそれをさらに推し進めます。ここでは、その中から 簡易名と X.509 証明書の 2 つだけを見ていきます。閉環境のカスタム アプリケーションを作成する場合は、ds:KeyName を使用する価値があります。

    <element name="KeyName" type="string"/>

署名を検証して名前をその内部ストアにマップし、該当するキーをフェッチするのは、プロセスの役割です。一般的な ds:KeyName の値には、電子メール アドレスかディレクトリ エントリが含まれています。

X.509 証明書は、ds:X509Data 要素によってサポートされます。この要素は、署名者が証明書 (Base64 でエンコードされた) か、証明書を識別するいくつかの代替形式 (サブジェクト名、発行者名、シリアル番号、キー識別子など) のいずれかを埋め込むことができます。署名者は、ドキュメントが署名された時点で署名者の身元が有効であったことを示すために、現在の証明書の取り消しリスト (CRL: Certificate Revocation List) のコピーを含めることもできます。以下のスキーマ フラグメントは、X.509 証明書を識別するさまざまな方法を示しています。

    <element name="X509Data" type="ds:X509DataType"/> 
    <complexType name="X509DataType">
      <sequence maxOccurs="unbounded">
        <choice>
          <element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
          <element name="X509SKI" type="base64Binary"/>
          <element name="X509SubjectName" type="string"/>
          <element name="X509Certificate" type="base64Binary"/>
          <element name="X509CRL" type="base64Binary"/>
          <any namespace="##other" processContents="lax"/>
        </choice>
      </sequence>
    </complexType>

    <complexType name="X509IssuerSerialType"> 
      <sequence> 
        <element name="X509IssuerName" type="string"/> 
        <element name="X509SerialNumber" type="integer"/> 
      </sequence>
    </complexType>

アプリケーションごとに異なるスキームを使用して証明書を保存および取得するので、通常、XML デジタル署名は同じキーに対して複数の名前を含んでいます。それらの名前は同じ ds:KeyInfo 要素内に埋め込まれています。この例では、ユーザーフレンドリ名 (GUI アプリケーションのポップアップ メニューに便利) と、発行者とシリアル番号の形式をとった一意の識別子 (ディレクトリ検索に便利) の 2 つが含まれています。

    <ds:KeyInfo>
        <ds:KeyName>
            rsalz@datapower.com
        </ds:KeyName>
        <ds:X509Data>
            <ds:X509SubjectName>
                cn=Rich Salz, o=DataPower, c=US
            </ds:X509SubjectName>
            <ds:X509IssuerSerial>
                <ds:IssuerName>
                    ou=Development, o=DataPower, c=US
                </ds:IssuerName>
                <ds:SerialNumber>32</ds:SerialNumber>
            </ds:X509IssuerSerial>
        </ds:X509Data>
    </ds:KeyInfo>

まとめ

スキーマ定義を使用して XML DSIG 仕様を広範囲にわたって考察し、使用可能な機能と XML DSIG ドキュメントの生成と検証に必要な処理について解説しました。基本的な署名要素 (ds:SignedInfo) から始め、次にアプリケーションコンテンツへの参照を組み込んでそのコンテンツを保護する方法を調べ、最後に ds:KeyInfo 要素の一部を調べてアプリケーションがどのようにして署名の検証や署名者の身元の認証を行うことができるのかを確認しました。これらの 3 つの側面は、XML (およびその他の) コンテンツの整合性の保護の最も基本的で最下位レベルの構成要素です。当然のことながら、柔軟性があるということは、直接使用が非常に難しいということを意味します。

WS-Security 仕様と共に使用することが、XML DSIG の最も戦略的な使用法の 1 つになるはずです。これによって、さらにアプリケーションを重視したデータ保護とユーザー認証の考え方が実現されます。WS-Security で XML DSIG がどのように使用されるのかを知りたい場合は、「WS-Security 入門」を参照してください。