この節では、Diffie-Hellman による鍵交換を使うサンプルを説明する。 cryptopp ライブラリには楕円曲線による DH も実装されているが、特許の問題があるので利用には注意すること。
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) { }
};
暗号器を親クラスにして実装する。 サンプルでは固定的に 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() では通常メッセージを許可しない。 その代わり、ハンドシェイクとして最初の鍵更新を送る必要がある。
void DhCtrl::init()
{
super::init();
m_dh.init();
enableNormalMessage(false);
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() で鍵更新を行う。
void DhCtrl::updateEncodeSecret()
{
m_dh.agreeKeyExchange();
Dh1024::UpdateMessage umsg;
postControlMessage(&umsg);
setEncodeSecret(m_dh.getSecret());
enableNormalMessage(true);
}
一定数のメッセージを投げたら鍵を更新する。 本来は暗号化の強度によって最も効率的な値を検討すべきだが、ここでは 256回毎とする。
void DhCtrl::endEncode(Context* pCtx)
{
if (pCtx->sequence[7] == 0) { //every 256 messages
sendMine();
}
}
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 {
;
}
}