はじめに
こんにちは。MATS(まっつ)と申します。5月初頭にアイディアとして投稿した「画像の複製・拡散を防ぐ方法1から3」につき、日本向けは2017年08月01日9:00から、海外向け英語版は同日17:00(PST 00:00)にビジネスサービス「JPEGcrypto(ジェイペグクリプト)」として実現し運用を開始しました。その技術的側面からの説明をここにしますが、その意図は、これまでアドバイスくださった皆さんに状況をご報告するとともに、これを使って先に挙げた課題事項が解消へと進むようQiitaメンバーの皆さんのエンジニアとしてのご支援(お知恵・アドバイス)を切にお願いするためであります。
なお、ECLIPSは画像と動画の両方に使えますが、処理内容が類似しているため、説明の簡便さを考慮して画像について述べます。ワードプレスに適用したサンプルでHTMLソースを見ながら読んでいただけるとわかりやすいかと思います。(以下、簡潔さのためにデスマス調ではなく「である」調で記述します。)
####1. サービス概要
このサービスは「あらかじめ特定の付加情報とともに本サービス向けデータとしてエンコード(以下、ECLIPSエンコード)した画像及び動画をブラウザにて表示する際、画像と同時にHTMLに記載されるJavaScriptを通じてサーバー側でデコードし、特定の付加情報等を判断要素としてブラウザでの表示/非表示を制御するウェブ・サービス」である。
#####1.1. ECLIPSサービスの機能
ECLIPS形式にエンコードされた画像をデコードして元どおりに表示するには、表示されているHTMLにインクルードされているeclips.jsとサーバー側のデコーダーが辻褄のあった連携した処理をすることのみで実現するため、これらが表示を許可した場合しか画像は表示できない。eclips.js自体がECLIPSシステムによって固有の情報を含めて動的に生成されており、eclips.jsを保存して改造してデコードさせようとしてもデコーダーは拒否をするし、HTMLソースを見てデコードされた画像のURLにアクセスしても拒否される。また、eclips.jsは画像がコピー・保存されないにくいよう継続的にブラウザを監視し、eclips.js自体を改造してこれを妨げようという試みも自身でブロックする。これらの機能を活かし、特定のドメインで特定の期間だけ画像を表示できるよう制御したり、ブラウザ画面で画像を見せたいがコピー・保存されないにくいようにする制御することができる。
[補足] ブラウザに表示できる以上は何らかの手段(具体的言及は避けるが)にて画像を保存することはできる。表示非表示ををコントロール(ドメインと期限)し、閲覧できても画像が保存できにくいようにし、保存されてもネットで拡散・転用されにくいように閲覧者の特定可能情報を画像にかぶせるという三段セットのアプローチを本サービスでは取ります。保存できないことのみに固執しないのは、このサービスが発案される背景となったネットの画像にまつわる問題の本質は、「ブラウザで閲覧できたらその見えた画像が保存できること」ではなく、「閲覧された画像が所有者・管理者のコントロールが及ばないところで雪だるま式に拡散されて閲覧者が増えること」だからです。所有者・管理者の許可のもとで閲覧できた人自身が見えた画像を保存できるのは良いのです。それが所有者・管理者の意に反して拡散しなければ問題は最小限に抑えられるのです。
#####1.2. ECLIPSサービス利用可能環境
Google Chrome, Microsoft Edge, Mozilla FireFoxでの正常な稼働を確認した(Apple Safariは正常稼働しない状況)。基本的にはJavaScriptが動くブラウザさえあればアプリケーションやプラグインをインストールする必要なく稼働する。この導入への障壁の低さから多くのウェブサイトで容易に導入してもらえる仕様になっている。しかしながら、正常に稼働しない環境も確認されており、例えばWordpressのプラグインであるJetPackがインストールされているウェブサイトでは正常にデコードされない。これは、JetPackがウェブサイトに表示する画像をwordpress.comのサイトに自動でアップロードし画像のURLをwordpress.com配下に変更する挙動特性によるものと認識している。このように、特殊な内部処理をするコードがインストールされた状況では正常に挙動しないことがあるので、使用前に各環境での検証をお願いしたい。
####2. 使用手順と処理の概要
ECLIPSを利用する手順はIT知識がない人でも見よう見まねで使えるような以下の簡単な手順にした。
(手順1)ECLIPSのウェブサイトで表示条件を指定して画像をエンコードする(アカウント作成が必須)
(手順2)エンコードした画像を通常の画像と同じ要領でHTMLのimgタグを用いてページに貼る
(手順3)imgタグの中にclassをprotect-imageとして指定する
(手順4)HTMLのheaderもしくはbodyにjQueryとECLIPS用JavaScriptをインクルードする
上記の早ければ1分で済む手順を踏んで記述されたHTMLがブラウザに表示された際に発生する通信と処理は下図(上段:ECLIPS無しの通常従来、下段:ECLIPS使用時)のようになる。
#####2.1. エンコードについて
エンコードはJPEG画像に対してサーバー側で行う(エンコード用ページはこちら)。その他のフォーマットの画像もサーバー側でJPEGに変換してエンコードする事が可能だが、現状ではPNGのみをImagickでJPEGに変換してからエンコードできるようにした。エンコードできる画像のサイズは3MBを上限とした。エンコード処理の内容は、JPEG画像の画像内容のデータ部分全体のみをECLIPS向けの固有の可逆変換エンコードをし、JPEGヘッダーはExifを削除する以外はそのままにするエンコードとした。このため、コンピュータはエンコード後の画像もJPEGと認識するが、表示される画像は真っ黒かグレーで表示され内容がわからないものになるようにした。なお、動画(MP4)に関しても同様の仕様で実現しており、データ内容の一部分ではなく全体をエンコードする仕様である。
< 画像を貼り表示したいページのURL >
エンコードされた画像が貼られ表示された時にECLIPSによってデコードされても良いページのURLを記載する。HTTP/HTTPSの区別はせず、このURL及びその配下のURLにてデコードリクエストが発生した場合にのみECLIPSサービスは「表示しても良い」と判定する。この設定によって、自分のウェブサイト以外で画像がデコードされることを防ぐことができる。また、他者に対して「貴方のウェブサイトでの利用に限りライセンスを与えます」のような画像のドメイン制限付きライセンスも可能になる。
< 表示許可期限日時 (UTC) >
特定の日時までを期限に画像をデコード許可することができる設定。時刻は日本時間ではなくUTC(JST-9)。特定の期日までのみ有効な情報を画像で表示したい場合や、期限付きライセンスの下で表示したい画像(例:タレントを起用した広告など)を制御することに使える。設定可能期限日時はエンコード時点から1年先まで。
< ウォーターマーク >
デコードされた画像にかぶせる文字やロゴ。これまでに画像の無断利用を防ごうとウォーターマークを画像に描画する手法は存在したが、その実効性は十分とは言えなかった。その理由は、全ての画像に描画されるウォーターマークが静的で同一であるために警告のメッセージは示せても無断コピーをした人物を特定する要因にはなり得ない実効性の不足であった。ECLIPSでは画像がデータとしてコピー・保存されること自体が困難になってはいるが、スクリーンショットを撮られたり何らかの方法で保存されネットで拡散された場合に拡散の出元を特定できる情報(日時・クライアント名)を動的に記載することで拡散を妨げることを狙う。
#####2.2. デコード
< eclips.js >
エンコードされた画像が貼られるHTMLに同時にインクルードされるJavaScriptである。内部処理にjQueryを使用しているためjQueryを同時にインクルードする必要がある。サーバー側で動的に生成され、呼び出された瞬間のみ内部処理が有効となるトークンを持つ。処理内容は、HTMLが読み込まれたタイミングをトリガーとしてHTMLを走査し、class=protect-imageであるimgタグがあればそのsrcをECLIPSサーバーにデコード依頼をするという単純な処理がメインである。なお、同様の処理をAjaxで行う場合はcross-domain問題がネックになるが、実現したい処理がAjaxでの非同期処理である必要がない事からAjaxは使わずcross-domain問題も発生しない仕様にした。
< class >
上記で述べたeclips.jsがデコード対象のECLIPS画像を判別するために使用するclassである。このクラスの有無によりデコードリクエストの有無を決める。idではなくclassにしたのは、言わずもがなではあるが、単一のHTMLには複数のECLIPS画像が貼られる事があるので、固有性を表すidではなく複数存在が仕様的に許容されているclassを使った。
< decoder >
ECLIPSサーバーにあり、上記のeclips.jsを使ってブラウザがデコードされた画像を取得するために通信する相手である。PHPで記述してある。デコーダーはブラウザから受け取った「エンコードされた画像のURL」にアクセスしてエンコードされた状態の画像を取得する。そして、エンコード時に指定された表示非表示条件(エンコードされた画像の中に格納されている)やその他の条件を元に判断し、表示してもOKの場合はブラウザにデコードされた画像を返す。デコードする際にはブラウザを特定する文字列を画像に記載して、著作権侵害や肖像権侵害時に閲覧者を特定する事ができるようにする。この文字列記載(ウォーターマーク)はImagickを使用して描画する。Imagickでのウォーターマーク描画はCPU&メモリー資源を意外と喰うようで、これの低減についてアルゴリズムの工夫をした。
ちなみに、ECLIPSでは一切の画像をサーバーに保存せず、デコード処理の中でもHDDに記録しない。デコードリクエストがあるたびに画像所有者が決めたURLにエンコードされた画像を取りに行く。これは、画像の所有者が表示非表示をコントロールしたい対象画像が、第三者であるECLIPSシステムに残されていることはコントロールの理念にそぐわないという考え方からである。画像の所有者が(エンコードされた状態であっても)画像をウェブサーバーから削除すれば完全にネットから抹消される状態を理想と判断し、通信料や処理時間はかかるがサーバーのスペック(通信速度・CPU/GPU速度・アルゴリズム)で補うこととしこの仕様にした。
デコード処理はGCP(Google Cloud Platform)に仮想化されクラスタリングされたウェブサーバー群にて行う。このような場合、多くの方がAWSを選択肢に挙げると思われるが、コスト計算の結果として本件ではGCPを選択肢した。
#####2.3. 認証サイト対応
上記で記載したデコード処理には「ECLIPSサーバーが画像にアクセスできる事」が条件として存在する。仕様案としては「クライアントのブラウザからECLIPSサーバーにエンコードされた画像をアップロードさせればいいのでは?」という疑問があると思う。これを採用しなかった理由は2つあり、
1 クライアントの通信量が画像データ1往復分多くなってしまうから
2 対象画像がイントラネットにある場合、イントラ外へのデータ流出を助ける事になるから
である。しかしながら「エクストラネットにあるが画像を閲覧するには認証が必要」というサイトで使用したい場合や「NDAを結べばイントラネットのファイアーウォールに認証付きの穴を開けるから使いたい」という場合もあるだろう。そこで、認証サイトの認証方式がBasic認証である場合は、ECLIPSサービスのアクセス向けに作成してもらったUID/PWDを元にして認証コードを生成し、それをHTMLのimgタグ内に記載してもらう事でECLIPSサーバーのみが認証を通過できる仕様を装備した。この認証コード自体もECLIPSエンコードされているため、それを見た閲覧者が勝手にUID/PWDを得て認証通過することはない。
#####2.4. 緊急処置への対応(非常停止)
画像をECLIPSでエンコードし、自分が管理者であるウェブサイトだけであればその画像を削除すれば済むが、他者が管理しているウェブサイト(他社サイト・SNSなど)に掲載されている時、現状では自身が所有する画像でありながらも表示を止める手段はない。ECLIPSでは「緊急非常停止ボタン」を用意しており、マイアカウントにログインしてこのボタンをワンクリックすれば、自分が管理者ではないサイトに貼られていたとしてもECLIPSサービスはデコードしないようになる。なお、非常停止はそのアカウントにてエンコードした全ての画像に対して一括に行われ、個別の画像については行わない。
####3. サービスとしての継続性を確保する(ビジネス観点)
技術仕様とは関係ない話です。このサービスは「エンコード」「デコード制御」という時間的な幅の中で提供されるサービスであるので、継続性がサービスの付加価値として絶対的に欠かせない。無料で提供できるのであれば無料で提供したいのだが、そうすると存続のために社会に支払うコストが確保できない。私自身、これまでに「企画・マーケティング」と「アーキテクト・エンジニア」の両方を歩いてきた半魚人のような職能の人間なので、自分の中で「お役に立つ最善をできるだけ安価で」というアーキテクト・エンジニアの思いと、「ビジネスとしての存続と成長が無くして社会貢献はない」というマーケッターとしての思いが常に綱引きしている。このような葛藤の中で、エンジニアとしてコスト最適化の観点からサービス継続性に寄与できる要素は以下ではないかと私は考えている。
#####3.1. 「何が問題で解決しようとしているのか」を常に意識すること
自分ひとり、コードひとつで全ての人に貢献することはできない。それを目指すと結局は「10得ナイフ」のような、一応全てには対応しているが実際には使われないものが出来上がる。組織として活動していると様々な観点から要件を提示されて、全てを取り込んだ結果、何がしたいのかわからない仕組みになるという有様を私自身沢山見てきた。これに囚われずにキーボードを叩けるかがエンジニアの戦いのように思う。
#####3.2. コードのアルゴリズムの徹底的な効率化
無駄な処理が無いか、もっと効率的なアルゴリズムは無いか、嫌になる程にコードを研ぎ澄ましたい。これには上記で述べた「何が目的なのか」が明確になっていて初めて可能なのだと思う。
#####3.3. コードを稼働させるサーバーの住み分け
ビジネスでは「存在を知らせる」「理解して納得してもらう」「お金を払ってもらう」のようにコストがかかる時点と利益が得られる時点にギャップが生じる。このギャップの狭間で発生することがあるのが「黒字倒産」であり、キャッシュフローはあるのに売掛金の回収前にコストへの支払いを迎えてしまい立ち行かなくなる事態である。このリスクを最小限にするため、コスト発生と支払い期限のギャップを小さくする工夫が必要である。パソコン販売の業界で言えば「BTO(Built To Order)」という言葉があるが、受注時に先に代金を払ってもらい、それを原資として製品を作るのがBTOである。ウェブサービスで言えば、キャッシュフローは生まない処理は定額料金のサーバーで、キャッシュフローを生む処理は従量制サーバーで処理速度・安定性を最優先して回すような住み分けが有効だと考える。
以上、2017年5月はじめに皆さんに相談した考え方をサービスとして運用開始し、これから人様のお役に立てるように回していく段になったので、心からの感謝の気持ちを込めてご報告するものです。エンジニアとして「ちょっと試しに使ってみるか」という方がおられたらQiita経由でお知らせいただければ喜んでお話ししますので、アドバイスいただければ幸いです。
最後になりますが、Qiitaという素晴らしいサービスを運営されている皆様にも敬意と感謝を申し上げます。
Mats (Electric Blue Industries Ltd.)
<修正記録>
- 本サービスを使用することで画像が絶対に保存できなくなるように読める記述を訂正し、「保存しにくくなる」との記述に修正しました。ブラウザに表示される以上は原理的に保存はできますので、その保存をするための敷居を上げ、保存された場合でも拡散元がわかるようにすることで拡散されにくくするというアプローチを取ります。ご指摘くださったユーザーさんに御礼申し上げます。 [2017-08-03 14:00 MATS]