QR コードへの隠しデータの埋め込み

突然ですが上の QRコード、お手元のアプリで読み取るとどんなテキストが表示されますか?

実はこの QRコード、読み取りアプリによって異なるテキストが表示されます。

iPhone用公式QRコードリーダーでの読み取り結果→"Pride and Prejudice"

Android用QRコードリーダーでの読み取り結果→"Pride and Prejudice and Zombies"

もちろん、URL が埋め込んであって接続先でアプリや端末を判定している訳ではありません。QRコードはその仕様上、色々といたずらできるようになっており、アプリ間でその対処が異なるために、このようなことが起きます。

という訳でこの文書では、QRコードに秘密のデータを埋め込む方法を紹介します。最近だと少額決済アプリにも使われるようになってきた QRコードですが、色々といたずらし甲斐のある ID システムであることを知っておいて損はないと思います。

QRコードの基本的な仕組

QRコードは「二次元バーコード」と呼ばれる、平面上に並べられた白黒のドット(QRコード用語ではこのドットを「モジュール」と呼びますが、本記事では「ドット」で通します)でデータを表現する ID システムです。

様々なデータを埋め込むことができるシステムで、URL のようなテキスト情報だけでなくバイナリデータも埋め込むことができますので、おおむねどんなデータでも埋め込めます。ただ、どんなデータがそこに埋め込まれているかをコード内で示す方法は規格として定められていませんので、リーダー側でそれを判別する必要があります。汎用の QRコードリーダーの場合、読み取ったデータはできるだけテキストとして解釈しようとします。例えば画像ファイルをそのまま埋め込んだとしても、それが画像ファイルであることを識別して表示するかどうかは、リーダーの実装次第となります。この実装に不備があれば、悪意をもって設計されたコードを読んだときにリーダーが誤動作する場合があります。

QRコードには強力なエラー訂正機構が採用されているため、ちょっとしたコードの汚れやカメラ側の問題は無視して使うことができます。その一方で、その QRコード自体が信用できるものであることを保証することはできません。目の前の QRコードを安易に読み取っていいものなのかどうかを QRコードリーダーは判断することができません。その信頼性は別の手段で判断する必要があります。

§

それでは具体的にデータを隠す手法を紹介します。

8ビットバイトモードで NUL 文字(\0)を埋め込む

まず、冒頭で紹介した、リーダーによって読めたり読めなかったりする隠し方を紹介します。

QRコードには「8ビットバイトモード」という、任意のバイト列を埋め込めるモードがあります。他に「英数字モード」という、いかにもテキスト用途のモードがあるだけに、画像などのバイナリデータ専用でテキストを埋め込むのには不向きのように思えますが、QRコードの英数字モードでは英大文字しか提供されていないため、小文字を含むテキストを埋め込んだり、UTF-8 でエンコードされたテキストを埋め込む目的で多用されています。そのため多くの QRコードリーダーでは、8ビットバイトモードでエンコードされたデータをまずはテキストとして解釈しようとします。

そのためか、NUL 文字を埋め込んだテキストをこれらのリーダーに読ませると、それを文字列の終端文字と解釈してデータの読み込みをそこで打ち切る場合があります。この特性を利用すると、隠しデータを埋め込むことができます。ただし、NUL 文字以降の文字列も表示できる QRコードリーダーも少なくありません。私が試した範囲では、以下の結果となりました。

プラットフォームアプリ名結果
iPhone公式QRコードリーダー "Q""Pride and Prejudice"
iPhoneGoogle Chrome内蔵リーダー"Pride and Prejudice"
iPhoneQRコード&バーコード"Pride and Prejudice"
iPhonei-nigma"Pride and Prejudice and Zombies"
Android公式QRコードリーダー"Q""Pride and Prejudice and Zombies"
AndroidBarcode Scanner"Pride and Prejudice and Zombies"
AndroidYahoo! QRコードリーダー"Pride and Prejudice and Zombies"

iPhone アプリのほとんどは NUL 文字以降を打ち切りますが、Android アプリはどれも NUL 文字以降も処理しています。C 言語ベースのアプリと Java ベースアプリの違いによるものでしょうか。

URL への適用は意外な結果

これをうまく利用すれば、リーダーによって見える URL が変わる QRコードができるんじゃないかなと思って作ってみたのがこちら。

このコードには、まず "https://fukuchi.org/works/qrhack/normal.html" という URL が埋め込まれていて、続いて NUL 文字の後に "secret.html" という文字列を埋め込んであるので、NUL 文字を読み飛ばすリーダーではこの URL が "https://fukuchi.org/works/qrhack/normal.htmlsecret.html"、と解釈されれば面白いと踏んで作ってみたものです。

ところがこれは目論見が外れました。Anrdoid アプリで認識された URL はアプリ上でクリッカブルになるのですが、NUL 文字のところで区切られて、その手前までしか URL として認識してくれませんでした。

終端パターンの後ろにデータを埋め込む

次に紹介するのは、一般のリーダーでは読み取ることのできないデータを隠す方法です。これは、次に挙げる QRコードの設計上の特徴を利用しています。

終端パターン
QRコードでは、埋め込まれるデータは連続した複数のチャンクの集合として形成されます。データの最後には、終端を表わすビットパターンを付けます。
パディングビット
大きなサイズの QRコードにデータを埋め込む場合に、QR コードに埋め込み可能なデータ容量に比べ、実際に埋め込むデータが小さくて容量が余る場合には、"0xec 0x11" というパディングビットを、容量を使い切るまで並べることになっています。大きな QRコードでドットパターンに周期性が見えることがあるのはこれが理由です。

さて、一般の QRコードリーダーは、終端パターンを見付けたらそこでデータのデコード作業を打ち切ります。その後ろにパディングビットが正しく並んでいるかどうかを検査するリーダーはまずありません。なので、パディングビットの代わりにデータを埋め込むことが可能になります。つまり、

[表に見えるデータチャンク] + [終端パターン] + [パディングビットチャンク]

となるべきところを、

[表に見えるデータチャンク] + [終端パターン] + [隠しデータチャンク] + [終端パターン]

とする訳です。

これをやるためには、まず QRコードのエンコードライブラリに手を加える必要があります。私が開発しているライブラリ libqrencode を改造して、終端パターンを自在に埋め込めるブランチを作成しました。

また、隠しデータチャンクを読める QRコードリーダーも必要になります。そこで Quirc というオープンソースのリーダーを fork して作成しました。

実験結果

次の QRコードが、作成した隠しデータ入りの QRコードです。例によって "Pride and Prejudice" という文字列の後ろに、"and Zombies" という隠しテキストが続いています。

これを各種 QRコードリーダーで読み込んでみたところ、どのアプリも終端パターン以降のデータを読み飛ばした上で、"Pride and Prejudice" を読み取り結果として提示しました。NUL 文字以降も読み取った Android アプリでも、

このように、終端パターン以降を読み飛ばしています。

このコードを改造した Quirc で読むと、以下の写真のように隠しテキストを読み取ることができます。終端パターンは '␀' で表示しています。

議論

以上に示したように、専用のリーダーでないと読めないデータを QRコードに埋め込む方法は確かに存在します。では、これを悪用することはできるのでしょうか。

私はセキュリティの専門家ではないので不確実なことしか言えませんが、安直な方法での悪用は難しいように思います。リーダー側に手を加えないと隠しデータは読めませんので、QRコードに手を加えるだけで、しかもバレないように(≒普通のリーダーからは不正のないように振舞いつつ)悪事を働くのはちょっとやり方が思いつきません。もちろん、リーダーの実装のセキュリティホールをつつく、というのはあるかもしれませんが。

一方で、これは技術的にはステガノグラフィ (steganography) に近いので、それに倣った使い方はあるかもしれません。例えば攻撃対象のシステムに潜入したプログラムが、対象システムから入手した情報を外部に運び出す過程で、QRコードに情報を隠す、といった手法はありえるでしょう。あるいは、隠しデータを読み込むことができるリーダーをこっそりと普及させておき、普段は大人しくしつつ隠しデータを読み取ったときに不正な操作をする、というようなやり方が考えられるでしょう。(そんなまだるっこしいやり方をやりたいかどうかは別として)

QRコードにデータを隠す手法を二つ紹介しました。二番目に紹介した手法は、ほとんどの QRコードリーダーが読み飛ばす「パディングビット」にデータを埋め込むというものでした。

本手法がただちに悪用可能であるかどうかの議論は別として、データの隠蔽を広範に防ぐことを考えるのであれば、多くの QRコードリーダーが、パディングビットもきちんとチェックし、不正なデータが含まれる QRコードをはじくことが必要となるでしょう。

追記

2018.8.30

なんと、QRコードの開発元が自ら、上記のような方法でコードに隠しデータを埋め込んでいたそうです。

上で「まだるっこしいやり方」と書いた方法を、まさか公式リーダーがやっていたとは…