
- 富山ホームページ/システム開発 グリークグリークトップ
- ブログ
- PHP
- 写真の中に秘密のメッセージを含めてみる (ステガノグラフィー) ブログ
写真の中に秘密のメッセージを含めてみる (ステガノグラフィー)
今の時代TwitterやInstagramなどで写真を見る機会が多くあると思います。
何気なく見ているその写真、本当にただの写真ですか?
スパイが写真に見せかけて暗号文をやりとりしてたら… などと考えるとわくわくしませんか?
ということでそんなことが可能かどうかやってみました。
結論:できる
今回の実験内容
このアルパカさんの写真にメッセージを埋め込んでみたいと思います。
メッセージを加工する
まずはじめにメッセージをBASE64エンコードします。LinuxかMacの方はコマンドでやってください。
printf "アルパカはフェ〜と鳴くよ" | base64
するとこうなります。
44Ki44Or44OR44Kr44Gv44OV44Kn44Cc44Go6bO044GP44KI
これを1文字づつ次の手順で処理していきます。
- 各文字をそれぞれのアスキーコードに変換する。(たとえば aは97, 3は51)
- 変換したものを8桁の2進数に変換する。 (たとえば 97 は 01100001)
- メッセージの長さを保存する領域48ビット分を先頭に追加する。
するとこうなります。
0000000000000000000000000000000000000001100000000011010000110100010010110110
1001001101000011010001001111011100100011010000110100010011110101001000110100
0011010001001011011100100011010000110100010001110111011000110100001101000100
1111010101100011010000110100010010110110111000110100001101000100001101100011
0011010000110100010001110110111100110110011000100100111100110000001101000011
0100010001110101000000110100001101000100101101001001
赤字がメッセージの長さ(384ビット)、残りが加工されたメッセージです。
これをPHPで実装するサンプルです。
<?php $message = 'アルパカはフェ〜と鳴くよ'; $encoded = base64_encode($message); $binary = ''; for ($i = 0; $i < strlen($encoded); $i++) { $binary .= sprintf('%08b', ord($encoded[$i])); } $length = sprintf('%032b', strlen($binary)); $binary = $length . $binary; var_dump($binary);
これでメッセージの加工ができました。
あとはそれを画像に埋め込むだけです。
画像へ埋め込む仕組み
コンピューターで表現される写真は細かい点の集合でできています。
当然それらの点には色が含まれています。
各色はRGBカラーモデルで表現されていて、Webの世界では rgb(255,0,0)や#ff0000のように表記します。
ちなみにどちらも赤を表しています。
これは光の三原色 R(赤), G(緑), B(青) それぞれに8ビットを割り当てたものですので、#ff0000を2進数で表現するとこうなります。
11111111 00000000 00000000
この内の各色末尾1ビットをメッセージ保存領域として使ってしまおうという話です。
つまり画像の一つの点(1ピクセル)あたり3ビットの情報を保管するわけです。
11111111 00000000 00000000
これによって色が微妙に変化することになりますが、このレベルだと人間の目には全く識別できないです。
やってみる
ひきつづきPHPをつかってやってみます。
加工したメッセージを3ビットずつ取り出して、画像1ドットに保管していきます。
<?php function generateBits() { $message = 'アルパカはフェ〜と鳴くよ'; $encoded = base64_encode($message); $binary = ''; for ($i = 0; $i < strlen($encoded); $i++) { $binary .= sprintf('%08b', ord($encoded[$i])); } $length = sprintf('%048b', strlen($binary)); $binary = $length . $binary; foreach (str_split($binary, 3) as $part) { yield str_split($part); } } if (!$image = imagecreatefrompng('alpaca.png')) { throw new \RuntimeException('Unable to load png resource'); } list ($width, $height) = getimagesize('alpaca.png'); $x = $y = 0; foreach (generateBits() as $bits) { $rgb = imagecolorat($image, $x++, $y); $r = ($rgb >> 16) & 0xFF; $g = ($rgb >> 8) & 0xFF; $b = $rgb & 0xFF; $color = imagecolorallocate( $image, bindec(substr(decbin($r), 0, -1) . $bits[0]), isset($bits[1]) ? bindec(substr(decbin($g), 0, -1) . $bits[1]) : $g, isset($bits[2]) ? bindec(substr(decbin($b), 0, -1) . $bits[2]) : $b ); imagesetpixel($image, $x, $y, $color); imagecolordeallocate($image, $color); if ($x > $width) { $x = 0; $y++; } } imagepng($image, 'alpaca_secret.png'); imagedestroy($image);
読み取る場合は
この仕様でメッセージを埋め込んだ場合の読み取り手順は
- 画像の左上から16ビクセル(48ビット分)取得し、メッセージの長さを取得
- メッセージの長さに必要な分残りのデータを取得
- 8ビットづつ10進数に戻して、ASCIIコード上の該当する文字に戻す
- すべてつなげたものをBASE64デコードする
完成品はこちら
Before![]() |
After![]() |
実用化するにはいくつか足りない点がありますが、これが基本的な仕組みです。
必要なものはこのあたりでしょうか?
- 画像に欠けがあっても復元できるように誤り訂正符号を含める
- 画像のサイズとメッセージ量に応じて色の末尾何ビットを利用するか判断する
この技術を応用したものがいわゆる「電子透かし」ですね。
これをライブラリ化したものをGithubで公開しています。
https://github.com/kzykhys/Steganography
まとめ
情報を色として保存することで、秘密のメッセージを画像に含めることができました。
今回の実験はこれで終了です。
ね、簡単でしょう?
