NEMのメッセージ暗号化の仕組み

NEM を使って XEM やモザイクを送金する際、任意でメッセージを付与することができます。

このメッセージは暗号化することができ、暗号化した場合は宛先アドレスの所有者にしかメッセージを閲覧することができません。

今回、nem-kotlin の方で暗号化メッセージの送受信の対応を行ったので、その際に調べたことなどをメモとして残しておきます。

要点

今回の要点です。

  • NEMのメッセージの暗号化は AES + CBCモードの共通鍵暗号化方式
  • 暗号化・復号化には 送信者の秘密鍵+受信者の公開鍵か、受信者の秘密鍵+送信者の公開鍵のいずれかが必要。
  • マルチシグアカウントから暗号化メッセージを送信することはできない
  • 送金を一度も行っていないアドレスに対して暗号化メッセージを送信することはできない

NEMのメッセージ暗号化の仕組み

最も詳細な説明は NEM の Technical Reference の 3.3 Encoding and decoding messages に記載があります。

AES暗号、CBCモードを使っていることが記載されています。

AES は共通鍵暗号化方式という、暗号化と復号化で同じ鍵を使う暗号化方式です。

共通鍵の生成

共通鍵をどのように生成するか、という計算式自体は上記 Technical Reference の記載を見ていくのが良いかと思います。

端折って説明すると、共通鍵を求める要素は3つあり、

  • 送信者Aの秘密鍵
  • 受信者Bの公開鍵
  • salt(32バイトの乱数)

となっています。共通鍵を求める関数を F(秘密鍵, 公開鍵, salt)と表現することにします。

この鍵生成の関数と、秘密鍵から公開鍵を生成する関数などを追っていくと、Fにおいて、下記が成り立ちます。

共通鍵 = F(Aの秘密鍵, Bの公開鍵, salt) = F(Bの秘密鍵, Aの公開鍵, salt)

送信者 A がこの鍵を使って A から B へのメッセージを暗号化した場合、復号化には Aの秘密鍵+Bの公開鍵か、Bの秘密鍵+Aの公開鍵のいずれかが必要ということになり、それ以外の第三者からは解読できないという仕組みのようです。

受信者 B は解読時に Aの公開鍵も使うため、このメッセージが A の秘密鍵を知るものによって暗号化されたものであるということの確認にもなります。

暗号化メッセージの構造

暗号化メッセージのバイナリ列には下記が並ぶ形になっています。

  • salt(32バイト)
  • IV(16バイト)
  • 暗号化されたメッセージ本文

IV (Initial Vector) は、16バイトの乱数です。

上述の AES の CBCモード は、暗号化の対象とするメッセージを、複数のブロックに分けて暗号化します。

CBC モードでは、暗号化を行う際に前のブロックの暗号化結果の XOR(排他論理和) をとります。

しかし、最初のブロックは前のブロックが存在しないため、その代わりとなるデータを与える必要があり、それが IV となります。

詳細はWikipedia参照

salt と IV に関しては 暗号化と復号化で同じものを使わないと正しく復号化できないため、暗号化メッセージ本体とそれらを連結したバイト列をトランザクションに含めて送信するということを行っています。

以上まとめると、こんな感じかと

(わやくちゃやな)

暗号化メッセージの注意点

上記でおおよそどのように暗号化・復号化を行っているのかわかったのですが、その性質を踏まえて気になったことがあったので動作を検証

マルチシグアカウントからは暗号化メッセージを送信することができない

最近 NEM JAPAN の Telegram でもちょっと話にあがってました。

マルチシグアカウントから送金トランザクションを発生させる場合、マルチシグアカウントそのものの秘密鍵は使わず、署名アカウントの秘密鍵を使って署名を行います。

このあたりの流れは以前下記ページに書いたのでそちらも参照ください。

マルチシグアカウントからの送信の場合、署名アカウントはマルチシグアカウントの秘密鍵を知らないはずなので、暗号化のための共通鍵を生成できず、暗号化できないということになります。

(理屈上は 署名者の秘密鍵と受信者の公開鍵 を使って暗号化し、受信者の秘密鍵と署名者の公開鍵を使って復号化すれば解読できる気もしますが、もとのルールとも変わってしまいますし、基本的にはできないという解釈で正しいように思います。)

ちなみに NanoWallet でもマルチシグで暗号化メッセージを送信しようすると、

「マルチシグアカウント経由では暗号化メッセージを送信できません」

と表示されます。

じゃぁ署名アカウント使わずにマルチシグアカウント自体から送金すれば、と思うかもしれませんが、マルチシグアカウントにした時点で、署名アカウントからしかトランザクションを発行できなくなっていますので、それもできません。

ちょっと注意が必要な仕様かも。

送金を一度も行っていアドレスに対して暗号化メッセージを送信することはできない

暗号化メッセージは鍵生成で受信者の公開鍵を使うため、それがわからないと暗号化をすることができません。

NEM のアカウントを新規に作成した直後は、アドレスから公開鍵を取得できない (/account/get で publicKey が null になっている) ため、このアドレスに対して暗号化メッセージを送ることができません。

作成直後のアカウントは NanoWallet 上でもトランザクション生成するまで公開鍵を確認することができません。

アドレス導出する過程でローカルでは公開鍵決定してるとは思うのですが、ネットワーク上に存在しないため確認できないということなのかなと思います。

このアドレスに対してNanoWallet から暗号化メッセージを送ろうとすると、

「受信者はネットワーク上で確認できる公開鍵を持っていません」

と表示されて暗号化メッセージを送ることができません。

暗号化メッセージをこちらに送ってねーみたいなことをする時は、事前にトランザクション発行して NanoWallet で公開鍵を確認できる状態にしときましょう。

さいごに

というわけで、nem-kotlin の暗号化メッセージ対応は v0.4.0 で対応済みです。

仕組みを追うと何か一つは新たな発見と言うか、気付きがありますね。


最後まで読んでいただきありがとうございます。 このブログを「いいな」と感じていただけましたら、Twiter にてフォローいただけるとうれしいです。ブログ更新情報などもお届けします。



この記事をシェアする




りゅーた
フリーランスのエンジニアしてます。Android、iOS アプリの開発、対向サーバの開発、C/C++のライブラリ開発が現在のメイン。趣味はテニス。3児の父。 もっと詳しく

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA