目次


J2ME/MIDPアプリケーションを保護する

Bouncy Castle Crypto APIを使ってワイヤレス・デバイス上でXML文書をディジタル署名および検証する方法

Comments

ワイヤレスWebサービスのJavaテクノロジー

JavaベースのWebサービスと、ワイヤレスJava開発は、JavaOne 2002の2大トピックでした。これらのトピックは、パーベイシブ・コンピューティングの世界における、将来のバックエンドおよびフロントエンド用Javaテクノロジーを象徴しています。

Webサービスは、疎結合の、相互運用可能なソフトウェア・コンポーネントであり、標準的なXML通信プロトコルに基づいています。Webサービスを使うと、ベンダーは自分の中核技術に特化した市場でサービスを提供できます。カスタマーは、自分のさまざまなニーズに基づいて、複数のベンダーからサービスを購入できます。こうした利便性があるので、Webサービスはワイヤレス・フロントエンドを提供するのに最適です。ワイヤレス情報デバイスの持つ利便性と、動的な特質のおかげで、モバイル・ユーザーは、モジュール化された、動的に再構成可能なバックエンド・サービスを利用できます。

Javaプラットフォームは、ワイヤレスWebサービス・アプリケーション開発の中で、いくつかの重要な役目を果たします。ワイヤレス側では、Java 2 Micro Edition (J2ME) が、携帯電話から複雑なホーム・ワイヤレス情報機器に至るすべてのワイヤレス・デバイスを対象に、クロス・デバイス互換性、高機能な言語フィーチャー、および広範囲に渡るライブラリーを提供します。J2MEのキー・コンポーネントにMobile Information Device Profile (MIDP) があります。これは、携帯電話やローエンドのPDAのJava APIと実行時環境を定義します。ローエンドのデバイスの数は非常に多いので、将来、MIDPは広く展開されるものと期待されています。

Webサービス側では、すでにJava 2 Enterprise Edition (J2EE) が、WebサービスのXMLメッセージ処理に必要なAPIとライブラリーをすべて備えています。EJBテクノロジー、JDBC API、およびRMI APIに実装されているJ2EEのコア機能を、Webサービスのインターフェースまたはゲートウェイを介することにより、容易に外の世界で利用可能なものにできます。これらのフィーチャーを1つにまとめて、ワイヤレスWebサービス・アプリケーションを使用可能にするため、J2ME Webサービスの仕様も提案されており、現在Java Community Processの中にあります (JSR 172)。

ワイヤレスWebサービスのセキュリティー

パーベイシブ・モバイル・コマースの世界におけるJavaベースのワイヤレスWebサービスの未来は明るいものですが、現在のテクノロジーは未成熟です。未解決のまま残されている問題に、セキュリティーの問題があります。ワイヤレス通信は電波傍受の格好の標的ですし、すべての通信データの強度暗号化機能をサポートできるほどの計算能力を備えたワイヤレス・デバイスもほとんどありません。その上、バックエンド側では、Webサービスが企業のファイアウォールの外側で実行されて、Webサービスどうしがオープンなメッセージング・プロトコルを使って対話します。同様にワイヤレスWebサービスも、多岐に渡るクラッキング攻撃の格好の標的となっています。SSL/TLSおよびHTTPSなどの良くできたPoint-to-Pointのセキュリティー・テクノロジーも、ベンダーや仲介者が複数存在するWebサービスのネットワーク形態には適していません。コンテンツをやりとりするための接続を保護することよりも、コンテンツ自体を保護することに焦点が当てられる必要があります。しかし、新たな困難があるにもかかわらず、Webサービス自体は、モバイル・コマースのセキュリティーを向上させるために使用することが可能です。Webサービスの新たなセキュリティー仕様を利用することにより、Webサービスをセキュリティー・ユーティリティーとして使用することが可能になります。

この先の部分では、セキュリティー技法として一般に使用されている、ディジタル署名について説明します。エンドツーエンドのデータの完全性を確実なものとするために、XMLメッセージの中でディジタル署名をどのように使用できるかを示します。いくつかの例を挙げ、ワイヤレス側にはポピュラーなJ2ME/MIDPプラットフォーム、バックエンド側にはJavaServer Pages (JSP) テクノロジーを使って、XMLディジタル署名を実装する方法を示します。最後に、現行のMIDPデバイスでディジタル署名を使用することのパフォーマンス上の問題と、その実現可能性について論じます。MIDPのプログラミングの詳細は、この記事の範囲を超えています。復習が必要な方は、「参考文献」のセクションを参照してください。

ディジタル署名を使用してデータの完全性を保証する

自分が株売買人であると想像してみてください。為替市場にいないときには、携帯電話を使って株価の変動を追うものとします。通勤途中、携帯電話を通して、自分のモニターしている株価がしきい値を下回ったという警報を受け取ります。さて、この情報をもとにしてこの株を買って、この安値を利用すべきでしょうか。何らかのアクションを起こす前に、その情報自体が信頼のおけるものかどうかを確かめたいと思うことでしょう。競争相手がメッセージを傍受して、それを改変 (株価の記号を変えるなど) できるとすれば、その競争相手はあなたに間違った株式を買うように仕向けて、自分の高価な株式をあなたに売り付けることができるかもしれません。どうすれば、モニター・サービスから携帯電話までの送信途中にメッセージが改ざんされていないことを確認できるでしょうか。

確かに、データの完全性は通信セキュリティーの最重要項目の1つです。物理的にセキュアなネットワークは非常に高価であり、地理的に広いエリアを網羅していません。会社が通信手段としてインターネットに頼らなければならない場合、インターネット自身にはセキュリティーがほとんど備わっていないという事実を直視しなければなりません。インターネットのデータ・パケットが宛先に到着するには、複数のルーターやホストを通過しなければならず、そうしたルーターやホストは、会話の両当事者の制御下にありません。ワイヤレス・インターネットのデータ通信は特に無防備です。

ここで助けに駆け付けてくれる道具が、Public Key Infrastructure (PKI) とディジタル署名です。(MIDPのプログラミング同様、ディジタル署名もこの記事の範囲を超えています。関心のある読者は、「参考文献」のセクションでさらに情報を探すことができます。)一言で言えば、PKIディジタル署名の方式では、当事者双方が2つの暗号化鍵を持ちます。すなわち、誰でも利用可能な公開鍵と、当人以外には秘密の私有鍵です。私有鍵で暗号化されたメッセージは、対応する公開鍵でなければ正しく暗号化解除できません。送信側は、メッセージを送信する際、メッセージと一緒に、同一のメッセージを私有鍵で暗号化したバージョンと、自分の公開鍵を送信することができます。受信側は、暗号化されたメッセージを、送信側の公開鍵を使って暗号化解除します。これが平文のメッセージと一致すれば、受信側はメッセージが確かに信頼のおけるものであることが分かります。メッセージを私有鍵で暗号化したバージョンは、完全性の検証トークンとして機能します。これを「ディジタル署名」と言います。

オリジナルのメッセージは非常に長い場合もあり、ディジタル署名を生成して検証するための公開鍵アルゴリズムはリソースを多く消費します。そのため、送信側は通常、「要約」と呼ばれるオリジナルのメッセージの短縮版を計算して、そのバージョンにだけディジタル署名をします。要約は、任意の長さの入力メッセージの、固定長で一方向のハッシュです。要約の計算は極めて高速です。受信側はまず、受信したメッセージから正しい要約が生成されることを確認します。要約が一致しなければ、メッセージは拒否され、その後、公開鍵のアルゴリズムは何も実行されません。これは、攻撃者が偽の公開鍵要求を多数送り付けてサーバーの計算リソースを圧倒する、目詰まりを起こすための攻撃を避ける上で役に立つかもしれません。

ほとんどの実用的なアプリケーションでは、公開鍵自体が信頼のおける機関によってディジタル署名されて、送信側の身元を検証する「ディジタル証明書」となります。しかし、ディジタル証明書の処理はこの記事の範囲を超えているため、以下の例では、送信側が信頼されているということと、送信側が未署名の公開鍵を使用するということを前提として、ここでのアプローチを説明します。

XMLディジタル署名の定義

前述のとおり、XMLはWebサービスの世界で主要なデータ交換プロトコルになりつつあります。Webサービスを動かすXMLメッセージは、宛先にたどり着くまでに、複数の仲介者を経なければならない場合があります。したがって、通信内容を初めから終わりまで保護することが重要です。その最良の方法は、XML文書とそのセキュリティー情報 (署名、要約、鍵など) を、単一の文書の形で一緒に送り出すことです。

XMLディジタル署名は、XML文書にディジタル署名を追加するためのW3Cの仕様です。送信側は、文書全体にディジタル署名するか、文書の一部にだけディジタル署名するかを選択できます。ディジタル署名、要約、および公開鍵は、XML要素内にフォーマットされます。これらの付加的なセキュリティー情報の要素は、オリジナルのXMLメッセージを包含することもできますし、逆にオリジナルのメッセージの中に組み込むこともできます。この記事では、便宜的に包含型のフォーマット設定を使用します。

分かりやすいものとするため、この記事で使用するXMLディジタル署名の例は、W3Cに完全準拠したものとはなっていません。たとえば、正規化 (XMLタグの標準化) の部分は割愛しています。これは、XML文書の適合性を確実なものとするに過ぎず、セキュリティー自体には関係ないからです。また、エンコードされた公開鍵証明書を使用する代わりに、鍵をいくつかのパラメーターに分割し、これらのパラメーターを公開鍵要素KeyInfo の下の別個のXML要素に渡しています。これにより、鍵と、鍵を処理するJavaコードの間の関係がより明瞭になっています。

MIDPアプリケーションにおけるXMLディジタル署名の処理

IBM alphaWorksは、XML Security SuiteというJavaパッケージを開発しています。これは、XMLディジタル署名の最新仕様をサポートしています。JSR 105は、XMLディジタル署名を処理するJava APIのセットを標準化するための、コミュニティーによる試みです。しかし、これらはJava 2 Standard Edition (J2SE) でしか機能しません。つまり、XML Security SuiteまたはJSR 105 Java XMLディジタル署名APIは、サーバー側でしか使用できず、MIDPワイヤレス・デバイス側では使用できないということです。

XMLディジタル署名を処理するには、使用するワイヤレス・デバイスが以下の機能をサポートしている必要があります。

  • XML文書との間でのデータの読み書き。この記事で扱うMIDPアプリケーションの例では、kXMLパーサーによってXML文書と要素が解析されて、Javaオブジェクトにされます (「参考文献」を参照)。kXML以外にも、さまざまなライセンス条件のもとで使用可能なMIDP XMLパーサーがいくつかあります。
  • メッセージへの署名と、署名の検証。これらの機能を実現するには、暗号化APIが必要です。これは、現行のMIDP 1.0仕様には含まれていません。

次のセクションでは、サーバー側とワイヤレスMIDPデバイス側の両方でXMLディジタル署名の生成と検証のために使用できる、軽量のJava暗号化パッケージを説明します。

Bouncy Castle Crypto API

Bouncy Castleは、Javaプラットフォーム用の、オープン・ソースの軽量な暗号化パッケージです。これは、多数の暗号化アルゴリズムをサポートし、JCE 1.2.1の実装を提供します。Bouncy Castleは、軽量であることを旨として設計されているため、J2SE 1.4からJ2ME (MIDPを含む) に至る各プラットフォームで実行できます。これは、MIDPで実行できる完全な暗号化パッケージとして唯一のものです。

強力であるとはいえ、Bouncy Castleパッケージには1つの大きな問題があります。つまり、ドキュメンテーションが不十分ということです。オンライン・ドキュメンテーションは存在しておらず、JavaDocも十分に記述されているとは言えません。他の先進的な暗号化パッケージと同様、Bouncy Castleパッケージは、一般的な概念と実装アルゴリズムを分離するために、型多相性を広く使用しています。初心者がクラスと、メソッド引数および戻り値の正しい型の間の関係を解読するのは困難です。デベロッパーは、物事の正しい行い方を調べるために、ソース・コードを調べたり、複数のケースをテストしたりする必要に迫られる場合があります。Bouncy Castleパッケージのガイドが大変よく整っていることは明らかです。

記事の残りの部分では、XMLディジタル署名の仕様、Bouncy Castleのさまざまな鍵生成プログラム、エンコード・エンジン、ディジタル署名プログラム、および要約エンジンについて扱います。

1つにまとめる

ここまで、多数のテクノロジーと概念を論じてきました。以下の部分では、処理全体を例で示します。すなわち、鍵を生成すること、サーバー側で文書に署名すること、文書をセキュアなXMLフォーマットにエンコードしてトランスポートすること、およびクライアント側で文書を検証することです。

  1. サーバーが、鍵モデル・パラメーターのセットを使い、ランダムな公開鍵と私有鍵の対を生成します。現実の実動システムの場合、このステップは通常不要です。鍵の対は生成済みで、サーバーの鍵ストアに保存されているのが一般的だからです。
  2. JSPページへのアクセスを受けると、サーバーが応答メッセージ用の要約を計算します。
  3. 次に、JSPページは署名プログラムを「署名」モードで呼び出し、私有鍵を使って要約用のディジタル署名を生成します。
  4. サーバーは、XML応答メッセージに、署名情報 (要約、ディジタル署名そのもの、および公開鍵パラメーター群を含む)を組み込みます。
  5. クライアントはXML文書を受信し、要約、ディジタル署名、および公開鍵パラメーター群を解析してJavaアプリケーション・データの形にします。
  6. クライアントは、平文のメッセージから要約を計算し、これをサーバーから受け取った要約と比較します。これら2つの要約が一致しなければ、文書の検証は失敗です。一致した場合、次のステップに移ります。
  7. クライアントは、組み込まれた鍵パラメーター群を使って公開鍵を再構成します。
  8. クライアントは署名プログラムを「検証」モードで呼び出し、署名を検証するために要約、署名、および公開鍵を渡します。

この先の部分では、上記のステップに従っていくつかの例を実装します。この例ではサーバー側とクライアント側の両方で同じBouncy Castle Crypto APIを使用するので、これに変更を加えて、ワイヤレス・デバイスでメッセージに署名し、サーバー側でこれを検証するようにすることは容易です。

要約の処理

前述のとおり、パフォーマンスを改善し、目詰まりを起こす(clogging)攻撃を避けるために、実際にはメッセージそのものにではなく、メッセージの要約に署名するようにします。リスト1に、SHA1Digest 要約エンジンを使用して、テキスト・メッセージから、エンコードされた要約を計算する方法を示します。

リスト1. エンコードされた要約の作成
static public String getDigest( String mesg ) throws Exception {
 SHA1Digest digEng = new SHA1Digest();
  byte [] mesgBytes = mesg.getBytes();
  digEng.update( mesgBytes, 0, mesgBytes.length );
  byte [] digest = new byte[digEng.getDigestSize()];
  digEng.doFinal(digest, 0);
  // Encode the digest into ASCII format for XML
  return (new String(Base64.encode(digest)));
}

以下のいくつかのセクションでは、Bouncy CastleのDSA、ECC、およびRSAの各署名プログラムを使って、ディジタル署名をしたり、これを検証したりする方法について説明します。これらの署名プログラムは、それぞれ異なるアルゴリズムと異なる鍵を使用し、必要なパラメーターも異なります。また、セキュリティー情報 (署名、要約、および公開鍵) をXML文書に組み込む方法についても説明します。最後に、3つの署名プログラムを比較し、将来に向けた改善点の提案を行います。

DSA署名の例

メソッドDSASigUtil.generateKeys() で鍵の対を生成します。前述のとおり、このステップは中央の認証局によってオフラインで実行されるのが一般的です。リスト2にこのステップを示します。

リスト2. 鍵の対の生成
// Get a secure random source.
SecureRandom sr = new SecureRandom();
 
// Generate DSA parameters.
DSAParametersGenerator DSAParaGen = new DSAParametersGenerator();
DSAParaGen.init(1024, 80, sr);
 
DSAPara = DSAParaGen.generateParameters();
 
// Get DSA key generation parameters.
DSAKeyGenerationParameters DSAKeyGenPara = new DSAKeyGenerationParameters(sr, DSAPara);
 
// Generate keys.
DSAKeyPairGenerator DSAKeyPairGen = new DSAKeyPairGenerator();
DSAKeyPairGen.init( DSAKeyGenPara );
AsymmetricCipherKeyPair keyPair = DSAKeyPairGen.generateKeyPair();
 
privKey = (DSAPrivateKeyParameters) keyPair.getPrivate();
pubKey = (DSAPublicKeyParameters) keyPair.getPublic();

生成された公開鍵は、パラメーターY で表現されています。これは、pubKey.getY() メソッドでリトリーブ(取り出し)されます。パラメーターGP、およびQ は、モデルを記述します。クラスDSAUtil の以下のメソッドは、モデル・パラメーターと鍵パラメーターをリトリーブします。これらのパラメーター群は、公開鍵オブジェクトを再構成するのに必要です。

リスト3. モデル・パラメーターと鍵パラメーターのリトリーブ
public static String getG() throws Exception {
  return (new String(Base64.encode(DSAPara.getG().toByteArray())));
}
public static String getP() throws Exception {
  return (new String(Base64.encode(DSAPara.getP().toByteArray())));
}
public static String getQ() throws Exception {
  return (new String(Base64.encode(DSAPara.getQ().toByteArray())));
}
public static String getY() throws Exception {
  return (new String(Base64.encode(pubKey.getY().toByteArray())));
}

ユーティリティー・クラスDSASigUtil は、生成された私有鍵を使用し、要約からRS という2つの部分で成るDSA署名を得ることができます。

リスト4. DSA署名のリトリーブ
static public String [] getSignature (String digest) throws Exception {
  // Sign
  DSASigner signer = new DSASigner();
  signer.init( true, privKey );
  BigInteger [] sigArray = signer.generateSignature( digest.getBytes());
 String [] result = new String [2];
  // Signature R
  result[0] = new String(Base64.encode(sigArray[0].toByteArray()));
  // Signature S
  result[1] = new String(Base64.encode(sigArray[1].toByteArray()));
 return result;
}

サーバーは要約、署名、および鍵パラメーター群をASCIIテキスト形式にエンコードし、そのテキストをXMLディジタル署名フォーマットに組み込みます。リスト5を参照してください。

リスト5. エンコードと、ディジタル署名フォーマットへの組み込み
<SignedMesg>
  <mesg>Hello World</mesg>
  <Signature>
    <SignedInfo>
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1" />
      <DigestValue>Ck1VqNd45QIvq3AZd8XYQLvEhtA=</DigestValue>
    </SignedInfo>
    <SignatureValue>
      <R>AMfVKyIUyPGdeUCtJxU+N9kQJc2x</R>
      <S>RwGahqpopPx//bMYXzH8dtY0lhA=</S>
    </SignatureValue>
    <KeyInfo>
      <KeyValue>
        <DSAKeyValue>
          <G>
            FgLTXVdxKAmDQtQHkDdFF5zthKSpQhUCzRgXxz7yzxM
            OLYrRoj5D8AXdGLS+5CzT4gu55MbO62dBfyEWKbWTIO
            6E+CuOfa53wvqjMl67tGxc8szgWWA6ZvRwVVVmJ6wqB
            m5hNLr7q1X2eJKQ+u3XYpFflJktOjV8O3zeEPOtsTQ=
          </G>
          <P>
            AOAu2WqVEKGTF8Zcxgde4vxc8f/Z+hk8A10M0AtY2lU
            8CX54dz2MuD6hOmhqGXJxIVlV9085d9D0yHcMv2wl9V
            Vt0/ww+aqFukCKZj9fHgZzq26nOBXMqibDo67J2vfQw
            EZMvCnyBXdS665whjzl5i7ubXu2Su+AqsodnvG9pyYB
          </P>
          <Q>AMjJUZy1RnQRqe/22BS83k2Hk8VR</Q>
          <Y>
            AM/9leouAW7nyON24xeqibMUpVOW8RyzcdNjp9NiPdfm
            HT42BvB4JL/cXx0tCbyHtcR5G+vALoOo7Mh3JJ+/gjx7
            sS8uHNngqx6O6dADrc9VdPvyllNDR0szLja1RTRCIy9M
            8p0dKe/U8iotAj2zctjfbrroMu/fTOBhkvb2gVvR
        </Y>
        </DSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</SignedMesg>

検証MIDPアプリケーションは、XML文書から得られた要約、鍵パラメーター群、および署名を解析し、公開鍵を再構成し、次のメソッドを使用して署名を検証します。

リスト6. 署名の検証
static public boolean verify (String digest,
                              String sig_r, String sig_s,
                              String key_g, String key_p,
                              String key_q, String key_y ) {
 BigInteger g = new BigInteger( Base64.decode(key_g) );
  BigInteger p = new BigInteger( Base64.decode(key_p) );
  BigInteger q = new BigInteger( Base64.decode(key_q) );
  BigInteger y = new BigInteger( Base64.decode(key_y) );
  BigInteger r = new BigInteger( Base64.decode(sig_r) );
  BigInteger s = new BigInteger( Base64.decode(sig_s) );
 DSAParameters DSAPara = new DSAParameters(p, q, g);
  DSAPublicKeyParameters DSAPubKeyPara = new DSAPublicKeyParameters(y,
                                             DSAPara);
 // Verify
  DSASigner signer = new DSASigner();
  signer.init( false, DSAPubKeyPara );
  boolean result = signer.verifySignature( digest.getBytes(), r, s );
  return result;
}

楕円曲線DSA署名の例

ECDSASigUtil クラスでは、まず使用する予定の楕円曲線モデルを定義します。リスト7を参照してください。

リスト7. 楕円曲線モデルの定義
private static BigInteger q = new
BigInteger("6277101735386680763835789423207666416083908700390324961279");
  private static BigInteger a = new
BigInteger("fffffffffffffffffffffffffffffffefffffffffffffffc", 16);
  private static BigInteger b = new
BigInteger("64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1", 16);
  private static BigInteger n = new
BigInteger("6277101735386680763835789423176059013767194773182842284081");
  private static byte [] G =
Hex.decode("03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012");

ECDSASigUtil.generateKeys() メソッドは、リスト7のモデルを使用して、ランダムな鍵の対を生成します。前述のとおり、このステップは中央の認証局によってオフラインで実行されるのが一般的です。

リスト8. リスト7のモデルを使用してランダムな鍵の対を生成
// Get a secure random source.
SecureRandom sr = new SecureRandom();
 
ECCurve.Fp curve = new ECCurve.Fp(q, a, b);
ECDomainParameters ECDomPara = new ECDomainParameters(curve,
                                                      curve.decodePoint(G),
                                                      n );
ECKeyGenerationParameters ECKeyGenPara = new ECKeyGenerationParameters(ECDomPara, sr);
ECKeyPairGenerator ECKeyPairGen = new ECKeyPairGenerator();
ECKeyPairGen.init( ECKeyGenPara );
AsymmetricCipherKeyPair keyPair = ECKeyPairGen.generateKeyPair();
 
privKey = (ECPrivateKeyParameters) keyPair.getPrivate();
pubKey = (ECPublicKeyParameters) keyPair.getPublic();

公開鍵はパラメーターQ で表現されています。これは、pubKey.getQ() メソッドでリトリーブされます。モデル・パラメーターq との混乱を避けるため、メソッドとXML要素名ではQQ を使用します (大文字のQ)。リスト9では、ECDSAUtil クラスのメソッド群を示します。これらのメソッドは、モデル・パラメーターと鍵パラメーターをリトリーブします。これらのパラメーター群は、公開鍵オブジェクトを再構成するのに必要です。

リスト9. モデル・パラメーターと鍵パラメーターをリトリーブするECDSAUtilメソッド
// public key specific field
public static String getQQ() throws Exception {
  return (new String(Base64.encode(pubKey.getQ().getEncoded())));
}
// Key parameter fields. Could also be retrieved from pubKey.
public static String getQ() throws Exception {
  return (new String(Base64.encode(q.toByteArray())));
}
public static String getA() throws Exception {
  return (new String(Base64.encode(a.toByteArray())));
}
public static String getB() throws Exception {
  return (new String(Base64.encode(b.toByteArray())));
}
public static String getN() throws Exception {
  return (new String(Base64.encode(n.toByteArray())));
}
public static String getG() throws Exception {
  return (new String(Base64.encode(G)));
}

ユーティリティー・クラスECDSASigUtil は、生成された私有鍵を使用し、要約からRS という2つの部分で成るDSA署名を得ることができます。

リスト10. DSA署名のリトリーブ
static public String [] getSignature (String digest) throws Exception {
  // Sign
  ECDSASigner signer = new ECDSASigner();
  signer.init( true, privKey );
  BigInteger [] sigArray = signer.generateSignature( digest.getBytes());
 String [] result = new String [2];
  // Signature R
  result[0] = new String(Base64.encode(sigArray[0].toByteArray()));
  // Signature S
  result[1] = new String(Base64.encode(sigArray[1].toByteArray()));
 return result;
}

サーバーは要約、署名、および鍵パラメーター群をASCIIテキスト形式にエンコードし、そのテキストをXMLディジタル署名フォーマットに組み込みます。リトリーブ・メソッド名と同様、対応するXML要素の中で、公開鍵パラメーターQQQ と表記して、鍵パラメーターq と区別します。リスト11を参照してください。

リスト11. エンコードと、ディジタル署名フォーマットへの組み込み
<SignedMesg>
  <mesg>Hello World</mesg>
  <Signature>
    <SignedInfo>
      <SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#dsa-sha1" />
      <DigestValue>Ck1VqNd45QIvq3AZd8XYQLvEhtA=</DigestValue>
    </SignedInfo>
    <SignatureValue>
      <R>NK/EIL2lrbFFCThnEuYlUWzh6IEfMsts</R>
      <S>AMeJDecKWrQO6Eeehl3het+FlDDL4IedCA==</S>
    </SignatureValue>
    <KeyInfo>
      <KeyValue>
        <ECKeyValue>
          <QQ>AwCiF5uG+DII/x1XTq84fLm4eGN2fED1PYc=</QQ>
          <Q>AP////////////////////7//////////w==</Q>
          <A>AP////////////////////7//////////A==</A>
          <B>ZCEFGeWcgOcPp+mrciQwSf643uzBRrmx</B>
          <N>AP///////////////5ne+DYUa8mxtNIoMQ==</N>
          <G>AxiNqA6wMJD2fL8g60OhiAD0/wr9gv8QEg==</G>
        </ECKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</SignedMesg>

検証MIDPアプリケーションは、XML文書から得られた要約、鍵パラメーター群、および署名を解析し、公開鍵を再構成し、リスト12のメソッドを使用して署名を検証します。

リスト12. 署名の検証
static public boolean verify (String digest,
                              String sig_r, String sig_s,
                              String key_q, String key_a,
                              String key_b, String key_n,
                              String key_G, String key_Q ) {
 BigInteger q = new BigInteger( Base64.decode(key_q) );
  BigInteger a = new BigInteger( Base64.decode(key_a) );
  BigInteger b = new BigInteger( Base64.decode(key_b) );
  BigInteger n = new BigInteger( Base64.decode(key_n) );
 byte [] G = Base64.decode(key_G);
  byte [] Q = Base64.decode(key_Q);
 BigInteger r = new BigInteger( Base64.decode(sig_r) );
  BigInteger s = new BigInteger( Base64.decode(sig_s) );
 ECCurve.Fp curve = new ECCurve.Fp(q, a, b);
  ECDomainParameters ECDomPara = new ECDomainParameters(
                 curve, curve.decodePoint(G), n );
  ECPublicKeyParameters pubKey = new ECPublicKeyParameters(
                 curve.decodePoint(Q), ECDomPara );
  // Verify
  ECDSASigner signer = new ECDSASigner();
  signer.init( false, pubKey );
  boolean result = signer.verifySignature( digest.getBytes(), r, s );
  return result;
}

RSA署名の例

RSAアルゴリズムのモデル・パラメーターは1つしかありません。次に示すExponent というパラメーターです。

private static BigInteger pubExp = new BigInteger("11", 16);

RSASigUtil.generateKeys() メソッドは、Exponent を使用して、ランダムな鍵の対を生成します。このステップも中央の認証局によってオフラインで実行されるのが一般的です。

リスト13. ランダムな鍵の対の生成
SecureRandom sr = new SecureRandom();
RSAKeyGenerationParameters RSAKeyGenPara = new RSAKeyGenerationParameters(pubExp, sr, 1024, 80);
RSAKeyPairGenerator RSAKeyPairGen = new RSAKeyPairGenerator();
RSAKeyPairGen.init(RSAKeyGenPara);
AsymmetricCipherKeyPair keyPair = RSAKeyPairGen.generateKeyPair();
 
privKey = (RSAPrivateCrtKeyParameters) keyPair.getPrivate();
pubKey = (RSAKeyParameters) keyPair.getPublic();

公開鍵はパラメーターModulus で表現されています。これは、pubKey.getModulus() メソッドでリトリーブされます。リスト14に、RSAUtil クラスのメソッド群を示します。これらのメソッドは、Exponent およびModulus (モデル・パラメーターと鍵パラメーター) をリトリーブします。これらのパラメーター群は、公開鍵オブジェクトを再構成するのに必要です。

リスト14. モデル・パラメーターと鍵パラメーターをリトリーブするRSAUtilメソッド
// Public key specific parameter.
public static String getMod() throws Exception {
  return (new String(Base64.encode(pubKey.getModulus().toByteArray())));
}
// General key parameter. pubExp is the same as pubKey.getExponent()
public static String getPubExp() throws Exception {
  return (new String(Base64.encode(pubExp.toByteArray())));
}

ユーティリティー・クラスRSASigUtil は、生成された私有鍵を使用し、要約からバイト配列RSA署名を得ることができます。

リスト15. バイト配列RSA署名の取得
static public String getSignature (String mesg) throws Exception {
  SHA1Digest digEng = new SHA1Digest();
  RSAEngine rsaEng = new RSAEngine();
 PSSSigner signer = new PSSSigner(rsaEng, digEng, 64);
  signer.init(true, privKey);
 byte [] sig = signer.generateSignature( mesg.getBytes() );
  String result = new String( Base64.encode(sig) );
  return result;
}

サーバーは要約、署名、および鍵パラメーター群をASCIIテキスト形式にエンコードし、そのテキストをXMLディジタル署名フォーマットに組み込みます。

リスト16. エンコードと、ディジタル署名フォーマットへの組み込み
<SignedMesg>
  <mesg>Hello World</mesg>
  <Signature>
    <SignedInfo>
      <SignatureMethod Algorithm="rsa-sha1" />
      <DigestValue>Ck1VqNd45QIvq3AZd8XYQLvEhtA=</DigestValue>
    </SignedInfo>
    <SignatureValue>
      IhJ/UMitJX7sWbzhnG8UKIdDYiZ0mfOUoAwemGiG08C
      WcQ3cUszgJXoIclHW/LN7w54w2FQyLStB+hPKASEC6r
      OjjgTBs6pwhjHCh2XxWx7hS7fdi9/Qk/ybH6xYGaeaZ
      3oHDBjFz3hEDtrvBYcHn3keCavncE22idRX7kBl8Do=
    </SignatureValue>
    <KeyInfo>
      <KeyValue>
        <RSAKeyValue>
          <Modulus>
            AKT1SyxSm4uT1zYWEPY9IaFY7vDhpkIM7FZeIQ
            OGnKeSEE5d3sPfONkCiHfO2oe4x6jNCXg/ngRi
            tmixBkjfKgHzF4trZZtNQZjfzAgcXGljzp9MD2
            ZEWQbHKvMZvZyJVrT2SlxLzusxWLwXdacprIDG
            bqDAmldBOBpkmrUdPpF9
          </Modulus>
          <Exponent>EQ==</Exponent>
        </RSAKeyValue>
      </KeyValue>
    </KeyInfo>
  </Signature>
</SignedMesg>

検証MIDPアプリケーションは、XML文書から得られた要約、鍵パラメーター群、および署名を解析し、公開鍵を再構成し、次のメソッドを使用して署名を検証します。

リスト17. 署名の検証
static public boolean verify (String mesg, String signature,
                              String mod, String pubExp) {
 BigInteger modulus = new BigInteger( Base64.decode(mod) );
  BigInteger exponent = new BigInteger( Base64.decode(pubExp) );
 SHA1Digest digEng = new SHA1Digest();
  RSAEngine rsaEng = new RSAEngine();
 RSAKeyParameters pubKey = new RSAKeyParameters(false, modulus, exponent);
 PSSSigner signer = new PSSSigner(rsaEng, digEng, 64);
  signer.init(false, pubKey);
  boolean res = signer.verifySignature( mesg.getBytes(),
                                        Base64.decode(signature) );
  return res;
}

パフォーマンスに関する考慮事項

テストしてみたところ、XMLの解析と要約の生成の両方を、ワイヤレス・デバイス上で非常に高速に実行できます。パフォーマンス上の主要なボトルネックとなるのは、予想どおり、低速な公開鍵アルゴリズムです。

Bouncy Castle Cryptoパッケージには署名クラスがいくつか用意されており、それぞれDSA、RSA、およびECCアルゴリズムを使ってメッセージの署名と検証を行います。しかし、それらのクラスすべてが現実のデバイスで実用可能というわけではありません。Bouncy Castle CryptoパッケージはあくまでもJava言語に基づいたものなので、最も手間のかかる大きな整数(big integer)の数値演算でさえ、特殊な最適化をせずに、低速のJVMに頼って実行します。

結果として、許容可能なパフォーマンスを備えているのはRSAアルゴリズムだけ、ということになります。それも、辛うじて許容可能と言えるに過ぎません。1024ビットの公開鍵による簡単なディジタル署名を検証するのに、16MHzのPalm VIIデバイスで1分強かかります。強度の劣る鍵を選べばパフォーマンスは改善できますが、どんな実用アプリケーションにせよ、ユーザー・インターフェースがロックしてしまわないようにするためには、検証プロセスをバックグラウンド・スレッドとして実行しなければなりません。

DSAとECCの両アルゴリズムのパフォーマンスは、現在の実装ではまったく許容不可能です。1024ビットの鍵を使ったDSA署名と、192ビットの鍵を使ったECC署名を検証するのに、標準的なPalm VII MIDPで1時間以上かかります。

パフォーマンスの問題から明白なとおり、大きな整数の数値演算と公開鍵アルゴリズム用に最適化されたJVMが必要です。JVMは、セキュリティー関連の数値演算の速度を上げるため、使用可能な特殊なハードウェアと、基盤をなすOSのフィーチャーを利用する必要があります。公開鍵アルゴリズムは、HTTPSなどのセキュア接続のハンドシェーク時に使用されます。現行のMIDP VMの多くは、妥当なパフォーマンスを発揮しつつ、HTTPSプロトコルをサポートしています。MIDP4Palm VMは、セキュア接続を確立するために、Palm OSの基盤となるinethttps プロトコルを利用できます。将来のVMとコア言語ライブラリーにより、セキュアな接続に関係する公開鍵の操作が最適化されるにとどまらず、ディジタル署名などの汎用セキュリティー機能が最適化できるようになることは、十分考え得ることです。

結論

この記事では、ワイヤレスWebサービスにおけるセキュリティーの重要性について扱いました。また、ワイヤレス側とWebサービス側の両方でXMLディジタル署名を処理する技法の例を示しました。ここでは、ディジタル署名を処理するために、純粋なJava実装であるBouncy Castle Java暗号化パッケージを使用しました。Bouncy Castleで提供されているものすべてのうち、ワイヤレス・デバイスで辛うじて許容可能なパフォーマンスを発揮するのはRSAアルゴリズムだけです。しかし、将来、MIDPランタイム環境が進歩するにつれ、ディジタル署名はモバイル・ユーザーにとって、もっと手軽に利用可能なものとなるかもしれません。


ダウンロード可能なリソース


関連トピック

  • この記事の例のサンプル・コードをダウンロードしてください。
  • "J2MEの発展" (developerWorks、2001年5月) では、Todd Sundstedが、J2MEによって、広範囲に渡るデバイスでデベロッパーがどのように自分のスキルを利用できるかを説明しています。
  • IBM alphaWorksXML Security Suite は、最新のXMLディジタル署名と、他のセキュアなXMLプロトコルをサポートしています。
  • W3C XMLディジタル署名の標準をご覧ください。
  • サーバー側のJavaプラットフォームでXMLディジタル署名を処理するには、この提案中のJava XML Digital Signature APIs について調べてください。
  • J2ME Web Services Specification は、XMLメッセージとWebサービスXML-RPCをJ2MEプラットフォーム上で処理するAPIを提案しています。
  • この記事では、例の中のXML文書を構文解析するのにkXML パーサーを使用しています。
  • 軽量なBouncy Castle Crypto 暗号化パッケージは、J2SEとJ2ME/MIDPプラットフォームの両方で実行できます。
  • developerWorksJava technologyゾーンで、Javaプログラミングに関する他の参考文献を調べてください。

コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology, XML
ArticleID=224168
ArticleTitle=J2ME/MIDPアプリケーションを保護する
publish-date=06012002