Ubuntu Weekly Recipe

第602回 2020年になったのでテキストに半角スペースで暗号文を埋め込もう

この記事を読むのに必要な時間:およそ 3.5 分

2020年が始まりました。⁠2020」という数字列を見ると何か見えてきませんか。そう,半角スペースですね。そこで今回は2020年にちなんで,テキストファイルに半角スペースを用いて暗号文を埋め込む方法を紹介しましょう。

テキストファイルにメッセージを埋め込める「stegsnow」

半角スペースはASCIIコードで「0x20」となります※1⁠。UTF-8な文化圏で生活している一般的なユーザーであれば,適当なファイルやストレージをバイナリダンプした際に,適度な間隔で「0x20」が登場するデータを見ることで「ここはなんか英文っぽいな」と判断することがよくあるでしょう※2⁠。hdコマンドやhexdumpコマンドを使う場合はASCIIの印字可能な文字もセットでダンプするので,英文ぐらいなら一発でわかるのですが,そういうことができないケースもあるのです。

※1
Unicodeにおける単なる空白文字としてはNon-break space(U+00A0)などもありますし,一般句読点(General Punctuation)ブロックには数多くの「なんとかスペース」が存在しますが,今回は無視します。
※2
UTF-16な文化圏で生活しているかたはそのままお帰りください。おつかれさまでした。

結果として「0x20」もしくはプレフィックスを省いた「20」の登場に過敏になります。そして「2020」という数字列を見て,⁠半角スペースが連続している!」と妄言を垂れ流すようになるのです。こいつはもうダメだ。

さて,半角スペース(ホワイトスペース)を含む空白文字は印字可能でありながら人類には認識できない文字として,古来より便利に使われてきました。たとえば英語ではわかち書きとして単語の区切り文字に使われていますし,日本でも闕字のように敬意を表すための用法として空白文字を挿入することがあります。人類の高位存在が備えているという特殊スキル「行間を読む」も一種の空白文字認識技法ではないかと考えられています。

コンピューターの世界でも,半角スペースやタブ文字,改行文字などの空白文字を組み合わせて記述できるプログラミング言語Whitespaceなんてものも存在します。

今回紹介する「stegsnow」はこんな「空白文字を活用したツール」の一種です。プレーンテキストの「行末」「半角スペース」「タブ文字」からなるエンコードされた文字列を挿入することで任意のデータを埋め込めます。

インストールは単にリポジトリからパッケージをインストールするだけです。

$ sudo apt install stegsnow

stegsnowでデータを埋め込む

まずはあらかじめ埋め込み対象のプレーンテキストを用意しておきます。これは任意の行長で改行されたテキストが想定されています。埋め込んだデータをやり取りすることから,メールの本文などをイメージすれば良いでしょう。

stegsnow自身は,元のテキストの行末に半角スペースで構築されたデータを追加します。特に指定しなければ各行が80文字以内に収まるようにデータを埋め込むようです※3⁠。ちなみに「80文字」の部分は-lオプションで変更可能です。

※3
正確には「80バイト」です。stegsnowは「1文字=1バイト」として計算しているところが多々存在します。

元データから埋め込みデータへのエンコードはタブ文字で区切られた最大7個の半角スペースによって実現しています。つまりタブ文字とタブ文字の間の半角スペースの数で,データを表すのです。結果的に個々のタブ文字の間に3bitのデータを埋め込めることになります。ASCII文字だと7bitのデータが必要なので,3つのデータ列に分割されるというわけですね。

さらに前述したとおり,最大行長も決まっているため,プレーンテキストごとに埋め込めるデータのサイズは異なります。1行あたりが短く行数が多いテキストならデータ容量は大きくなり,1行あたりが長く行数が少ないテキストならデータ容量は小さくなります。

今回は例として,青空文庫の銀河鉄道の夜から冒頭部分を段落区切りに空行をはさみ,行長を半角70文字におさえたテキストファイルを「body.txt」として用意しておきます。

「ではみなさんは、そういうふうに川だと言われたり、乳の流れたあとだと言
われたりしていた、このぼんやりと白いものがほんとうは何かご承知ですか」
先生は、黒板につるした大きな黒い星座の図の、上から下へ白くけぶった銀河
帯のようなところを指しながら、みんなに問いをかけました。

 カムパネルラが手をあげました。それから四、五人手をあげました。ジョバ
ンニも手をあげようとして、急いでそのままやめました。たしかにあれがみん
な星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎
日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなことも
よくわからないという気持ちがするのでした。

 ところが先生は早くもそれを見つけたのでした。

「ジョバンニさん。あなたはわかっているのでしょう」

 ジョバンニは勢いよく立ちあがりましたが、立ってみるともうはっきりとそ
れを答えることができないのでした。ザネリが前の席からふりかえって、ジョ
バンニを見てくすっとわらいました。ジョバンニはもうどぎまぎしてまっ赤に
なってしまいました。先生がまた言いました。

「大きな望遠鏡で銀河をよっく調べると銀河はだいたい何でしょう」

 やっぱり星だとジョバンニは思いましたが、こんどもすぐに答えることがで
きませんでした。

 先生はしばらく困ったようすでしたが、眼をカムパネルラの方へ向けて、

「ではカムパネルラさん」と名指しました。

 するとあんなに元気に手をあげたカムパネルラが、やはりもじもじ立ち上が
ったままやはり答えができませんでした。

このテキストに「けれども本当の幸いは一体なんだろう」というデータを埋め込んでみましょう。

$ stegsnow -m "けれども本当の幸いは一体なんだろう" body.txt output.txt
Message exceeded available space by approximately 29.52%.
An extra 4 lines were added.

-mでは埋め込むデータを指定し,その他の引数として埋め込む対象のテキストファイル名と,埋め込んだあとのデータを保存するテキストファイル名を指定します。それぞれ省略することで標準入力からテキストを受け取り,標準出力にテキストファイルを生成することも可能です。

最後のメッセージではデータが入り切らなかったので,4行余分に追加した,と記録されています。

実際に生成されたテキストファイルを開いてみると次のように表示されます。

図1 一部の行末に半角スペースとタブ文字が追加されている

画像

タブは>...で,半角スペースは赤背景の空白で表示しています。また右の縦のラインは80文字の部分です。

上記を見る限り,日本語が含まれる半角70文字幅の行の後ろには何もデータが入っていませんね。これはstegsnowが「1バイト=1文字」と換算していることによる弊害です。日本語テキストで半角70文字分の幅は,UTF-8だとおよそ105バイト前後になってしまいます。空行と一部の短い行以外は80バイトを超えているために,stegsnowは空行と一部の短い行にしかデータを追加できないと判断してしまっているのです。stegsnowがデータを追加できる行が限られている結果,すべてのデータを埋め込むためにファイル末尾に空行を追加する必要があったというわけです。

たとえば-Sオプションを付けることで,そのテキストファイルに埋め込めるデータ容量を確認できます。

$ stegsnow -S body.txt
File has storage capacity of between 317 and 335 bits.
Approximately 40 bytes.

今回のファイルだとたかだが40バイト程度であり,それ以上だと空行を追加しなくてはなりません。

最大行長を指定する-lオプションを利用すると,この制限を変えられます。たとえば半角70文字分に半角10文字分のデータを埋め込むとしたらおおよそ115バイト前後になります。というわけで,次のように実行してみましょう。

$ stegsnow -l 120 -m "けれども本当の幸いは一体なんだろう" body.txt output.txt
Message used approximately 63.75% of available space.

今度は行の追加が必要なかったようですね。

図2 日本語の行の末尾にもデータが追加されている

画像

今回は右の縦のラインは120文字の部分です。右端の位置がまちまちですが,これはUTF-8が可変長であるためです。たとえば1行目は日本語の部分が106バイトになっています。よって末尾の容量は14バイトとなります。stegsnowの1データあたりの最大長はタブ1文字+半角スペース7文字の8バイトなので,1データしか入らないというわけです。

さて次は出力されたファイルから,埋め込まれたデータを取り出してみましょう。これはstegsnowコマンドにファイルを渡すだけです。

$ stegsnow output.txt
けれども本当の幸いは一体なんだろう

日本語のデータもきちんと取り出せましたね。ちなみに上記のような-mオプションの使い方だと,末尾に改行は含まれないということだけ注意しておいてください。改行を含むデータを渡したい場合は,-m メッセージではなく-f ファイル名のように別ファイルにデータを保存した上でそれを渡すと良いでしょう。

さらにファイル名を指定すると,標準出力ではなくファイルに取り出したデータを保存します。バイナリデータを埋め込みたい時に使用してください。

$ stegsnow output.txt message.txt
$ cat message.txt
けれども本当の幸いは一体なんだろう

著者プロフィール

柴田充也(しばたみつや)

Ubuntu Japanese Team Member株式会社 創夢所属。数年前にLaunchpad上でStellariumの翻訳をしたことがきっかけで,Ubuntuの翻訳にも関わるようになりました。