DhCtrl

DhCtrl

この節では、Diffie-Hellman による鍵交換を使うサンプルを説明する。 cryptopp ライブラリには楕円曲線による DH も実装されているが、特許の問題があるので利用には注意すること。

DHで通信中に鍵更新を行えば、固定的な鍵を使うことによる ARC4 の脆弱性を大きく改善することができる。 しかし DH には中間者攻撃に弱いという欠点があるので、サンプルを使う際は注意すべきである。 一般的には、
  • MQV などの認証を含めた交換プロトコルを用いるようにする
  • 最初の鍵交換に使う暗号化鍵を秘匿する
といった対処を行う。

設計

DH のためには通信が必要である。 ここではコントロールメッセージを使うことにする。

折角通信するので、最初の鍵交換完了をハンドシェイク代わりに使う。 enableNormalMessage() は鍵交換完了時に行う。

自分が鍵ペアを生成し、相手から公開鍵をもらえば新たな共有鍵を作ることができる。 一方的に公開鍵を送りつければよい。 タイミングがずれすぎても問題なので、相手から公開鍵を受け取った時に自分がまだ投げていなければ、投げるようにする。

鍵の交換はこれでできるが、相手がいつ鍵を変えたかはわからない。 鍵の更新を知らせるメッセージを投げることにする。

鍵更新メッセージは、新たな鍵を使う直前に投げる必要がある。 メッセージ送信の排他制御の中、具体的には endEncode() のタイミングで送るようにする。

公開鍵メッセージ

DH のアルゴリズムによって共有情報の長さは固定である。 暗号器が必要とする長さに満たない場合は、複数の鍵交換を 1つのメッセージにまとめてしまう。

      class ExchangeMessage: public nine::UserMessage< 0x0EC0, ExchangeMessage >
      {
         public:
            enum { NUM = (SECRET_LEN + DH_VALUE_LEN - 1) / DH_VALUE_LEN };
            uint8_t pubkey[NUM][DH_PUB_LEN];

            virtual int getMarshalSize() const { return NUM * DH_PUB_LEN; }
            virtual void marshal(nine::Buffer* pBuf) const
            { pBuf->writeArray(&pubkey[0][0], sizeof(pubkey)); }
            virtual void unmarshal(nine::Buffer* pBuf)
            { pBuf->readArray(&pubkey[0][0], sizeof(pubkey)); }
      };

鍵更新メッセージ

鍵更新通知には何も付加情報は必要ない。

      class UpdateMessage: public nine::UserMessage< 0x0EC1, UpdateMessage >
      {
         public:
            virtual int getMarshalSize() const { return 0; }
            virtual void marshal(nine::Buffer* pBuf) const { }
            virtual void unmarshal(nine::Buffer* pBuf) { }
      };

DhCtrl

暗号器を親クラスにして実装する。 サンプルでは固定的に Arc4HmacCtrl を用いているが、テンプレートにした方が良いだろう。

DH はデータを改変しないので、encode, decode 処理は不要である。

先に書いたように、encode 排他処理の中で鍵更新メッセージを投げる必要がある。 これは endEncode() で行うので、これは実装する。

そして、相手からの鍵更新メッセージを扱う必要がある。 コントロールメッセージの受信は handleControlMessage() に渡されるので、これを実装する。

後は、鍵交換メッセージが成立したかどうかを判定する関数をいくつか追加する。

class DhCtrl: public Arc4HmacCtrl
{
      typedef Arc4HmacCtrl super;
   public:
      DhCtrl();
      virtual ~DhCtrl();

      virtual void init();

      virtual void endEncode(Context*);
      virtual void handleControlMessage(nine::Message* pMsg, Context*);

   protected:
      Dh1024 m_dh;
      bool sendMine();
      bool receiveYours(const Dh1024::ExchangeMessage* pMsg);
      void updateEncodeSecret();
      void updateDecodeSecret();
};

init

上に書いたように、init() では通常メッセージを許可しない。 その代わり、ハンドシェイクとして最初の鍵更新を送る必要がある。

void DhCtrl::init()
{
   super::init();
   m_dh.init();
   enableNormalMessage(false);
   sendMine();
}
sendMine() は、自分の公開鍵を送る関数である。
bool DhCtrl::sendMine()
{
   bool ret;
   bool complete;

   Dh1024::ExchangeMessage* pMsg = m_dh.generateMine(&complete);
   ret = postControlMessage(pMsg);
   if (ret && complete) {
      updateEncodeSecret();
   }
   return ret;
}

既に相手からの鍵交換メッセージを受信していたら complete が真になる。 complete時は、updateEncodeSecret() で鍵更新を行う。

encode鍵更新関数

encode 鍵更新は以下の 3つの作業を行う必要がある。
  1. 共有鍵を計算する
  2. 鍵更新メッセージを投げる。encodeロック中で行うこと。
  3. encode鍵を更新する
  4. ハンドシェイク完了とみなして通常メッセージを許可する。encodeロック中で行うこと。
void DhCtrl::updateEncodeSecret()
{
   m_dh.agreeKeyExchange();

   Dh1024::UpdateMessage umsg;
   postControlMessage(&umsg);
   setEncodeSecret(m_dh.getSecret());

   enableNormalMessage(true);
}

能動的な鍵更新

一定数のメッセージを投げたら鍵を更新する。 本来は暗号化の強度によって最も効率的な値を検討すべきだが、ここでは 256回毎とする。

回数判定と更新処理は endEncode() で行えばよい。
void DhCtrl::endEncode(Context* pCtx)
{
   if (pCtx->sequence[7] == 0) { //every 256 messages
      sendMine();
   }
}

コントロールメッセージ受信

この実装で使うコントロールメッセージは、鍵交換メッセージと鍵更新メッセージの 2つがある。 それぞれについて行うべきは:
鍵交換メッセージ
受け取ったら公開鍵を記憶。
その時、自分が公開鍵を投げていなければ生成して投げる。
自分と相手のDH情報は揃ったので、encode鍵を更新する。
鍵更新メッセージ
受け取ったら、次のメッセージから decode用の鍵を更新する。
void DhCtrl::handleControlMessage(nine::Message* pMsg, Context* p)
{
   if (pMsg->header.messageId == Dh1024::ExchangeMessage::MESSAGE_ID) {
      receiveYours(static_cast< Dh1024::ExchangeMessage* >(pMsg));
   } else if (pMsg->header.messageId == Dh1024::UpdateMessage::MESSAGE_ID) {
      updateDecodeSecret();
   } else {
      ;
   }
}