【大阪 / 横浜 / 徳島】インフラ / サーバーサイドエンジニア募集中!

【大阪 / 横浜 / 徳島】インフラ / サーバーサイドエンジニア募集中!

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【WordPress 専用】クラウドサーバー『ウェブスピード』

【WordPress 専用】クラウドサーバー『ウェブスピード』

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【予約システム開発】EDISONE カスタマイズ開発サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【200ヶ国以上に対応】グローバル eSIM「ビヨンドSIM」

【200ヶ国以上に対応】グローバル eSIM「ビヨンドSIM」

【中国への旅行・出張・駐在なら】中国SIMサービス「チョコSIM」

【中国への旅行・出張・駐在なら】中国SIMサービス「チョコSIM」

【グローバル専用サービス】北米・中国でも、ビヨンドのMSP

【グローバル専用サービス】北米・中国でも、ビヨンドのMSP

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

JPEGのExif情報をとっかかりにバイナリデータと戯れる

こんにちは。
開発チームのワイルド担当、まんだいです。

先日、スマートフォンの画像があさっての方を向くという怪奇現象に遭遇した際、Exif情報について色々調べた訳ですが、その過程でExif情報のバイナリが読めるようになったので、Exif情報の構成を解説したいと思います。

また、その過程を通じて、バイナリデータってチョロいんだという事を知ってもらえたらといいなと思っています。

 

Exif解析に使うもの

Exifの解析に使ったものは、インターネットで手に入ります。
以下の3つがあれば、Exif情報を読取る事は可能です。

まずは、規格の情報。
一般社団法人 電子情報技術産業協会の資料を完璧に理解できれば、余裕なのでしょうが、中々難解な書物なので、エクセルなんかを使って、一つ一つ確認していきました。

バイナリデータの参照には、Stirlingというソフトウェアを利用しました。
既にメンテナンスは終了しているようですが、この分野においてこれを超えるツールは、まだ見かけないくらい、良い作りです。

Linux環境の場合、バイナリデータを表示するコマンドとして、odというコマンドがあるので、od -x [path]で、だいたい同じようなものを見ることが出来ます。
この場合は、ダンプリストがズラーッと流れてしまうので、適宜headコマンド、tailコマンド、grepコマンドを駆使して、見たい部分だけ表示するようにします。

Exifが採用されている画像フォーマットは、JPEG、TIFF、JPEG XRとなっています。
これらのファイルには、Exifが登録されている事が多いため、サンプルはどこにでもありますね。
適当にスマートフォンで撮った写真を取り込んでみましょう。

 

バイナリ情報を読み解いてみる

バイナリデータは、テキスト情報ではなく、拡張子に表されるフォーマットに則ったデータの集合ですから、出だしの部分から、JPEGデータのフォーマットに沿ったデータの並び方をしています。

手元にあったJPEGファイルの先頭部分を抜き出してみました。

FF D8 FF E1 6C 0F 45 78 69 66 00 00 49 49

 
JPEGファイルは、必ず「FF D8」という2バイトのデータから始まります。
JPEG業界では、SOIと呼ばれる特殊な領域で、ここからしばらく、画像のメタデータを保存する領域が続きます。

メタデータとは、画像データ以外に保持しているデータの事で、Exif情報もこの中に含まれます。
このデータは必須項目と、それ以外の撮影した機器や、編集したアプリケーションが独自に付与できるデータに分けられます。

続くデータは「FF E1」で、これは、APP1アプリケーションセグメントというメタデータのセグメントの開始を表します。
これは可変長のデータなので、続く2バイトに、APP1アプリケーションセグメントのデータ長が格納されています。
データの長さは「6C0F」バイトなので、APP1アプリケーションセグメントのデータは、6C13アドレスまでになります。

続きデータは「45 78 69 66 00 00」で、これはExif情報の有無を示しています。
今回のデータは存在するようです。

ここまでがAPP1セグメント全体に関わる情報になります。

ここから先は、付属情報で必須ではありません。

続きのデータを抜き出してみましょう。

49 49 2A 00 08 00 00 00

 
まず始まるのは、TIFFヘッダで、バイトオーダー、TIFFバージョン、0th IFDの開始位置までのオフセット情報がここで表されています。
バイトオーダーは、今回は「49 49」でリトルエンディアンでデータが並んでいる事を表します。
ビッグエンディアンの場合は「4D 4D」となります。
以降、リトルエンディアン、ビッグエンディアンの違いで、各情報の並び順が逆になりますので、Apple製品で撮影した写真がお手元にある場合は、ビッグエンディアンなので、注意が必要です。
バイトオーダーという言葉の説明は、Wikipediaが詳しいかなと思います。
また、このデータの開始位置がこの先に出てくるデータアドレスのオフセットの基準位置になりますので、割と重要なポジションです。

その次がTIFFバージョンで、「2A 00」となっています。

続いて、0th IFDというものが出てきます。IFDは、Image File Directoryの略で、付属情報のまとまりを表します。
一つのIFDの中に、幾つかのタグがまとめられているというイメージです。
データの種類によって、IFDが分けられています。

IFD内のデータはタグ毎に分けられていて、それぞれのタグは12バイトの長さと決まっています。
IFDのデータ構造は以下のようになっています。

IFDの構造
0th IFD タグ数 2バイト
タグ1 12バイト
タグ2 12バイト
タグ3 12バイト
・・・
タグN 12バイト
1st IFDへのオフセット値 4バイト
0th IFD内の値 可変
  • 「タグ数」は、0th IFD内に何個のタグがあるかを表します。
  • 「1st IFDへのオフセット値」は、続くIFD(0th IFDの場合は1st IFD、1st IFDの場合は2nd IFD)が始まるアドレスまでのオフセット値になります。(ここが0の場合は、次のIFDはないという意味になります)
  • 「0th IFD内の値」は、各タグに割り当てられたバイト数で収まらないデータが入ります。(構造の仕組みは後ほど)

各タグの構成は以下のようになっています。

タグの構造
0th IFD タグ1 タグ番号 2バイト
タイプ 2バイト
カウント 4バイト
値までのオフセット値 4バイト
タグ2 タグ番号 2バイト
タイプ 2バイト
カウント 4バイト
値までのオフセット値 4バイト
  • 「タグ番号」は、結構な種類があって、列挙しきれません。仕様書のこの辺りから始まるのを、照らし合わせていくのが、一番確実かと思いますが、結構やってられない感もあるので、Exifの取得に関係あるものは都度紹介していきます。
  • 「タイプ」は、データ型です。型付けがある言語でのプログラミングを経験された方にはよく分かる、あれです。8種類しかないので、後ほどご紹介します。
  • 「カウント」は、タグ内に何個のデータが収められているかを表します。
  • 「値までのオフセット値」は、4バイトよりも大きいデータの場合、この中には入らない為、別途可変領域を割り当てて値を入れるようにする仕組みのため、そのデータへの場所へのオフセット値になります。例外があって、4バイト以下のデータはこのオフセット値用の部分にデータを入れてしまいます。

タイプを一覧にしてみました。
タイプによって、カウントの取り方が違うので、合わせて記載しました。

タイプの一覧
タイプの値 説明 カウントの取り方
1 BYTE 8ビット符号なし整数
2 ASCII 文字列。文字数には、文字列の終端を表す最後のNULLも含める 「BEYOND」の場合は、「B」「E」「Y」「O」「N」「D」「\0」 → 7カウント
3 SHORT 16ビット(2バイト)符号なし整数 5の場合は1カウント
4 LONG 32ビット(4バイト)符号なし整数 5の場合は1カウント
5 RATIONAL LONG2個分で、最初のLONGを分子、2個目のLONGを分母とする 5/4の場合は1
7 UNDEFINED フィールドの定義により、どんな値をとってもいい8ビットのデータ 0xFF、0x01、0x45、0x11、0xDD、で5カウント(8ビットで1カウントのため)
9 SLONG 32ビット(4バイト)符号付き整数 5で1カウント
10 SRATIONAL SLONG2古文で、最初のSLONGを分子、2個目のSLONGを分母とする 5/4で1カウント

表だけ見ても、イメージは沸かないと思うので、データを見ながら一つずつ見ていきましょう。

 

IFDを読んでみる

実際の写真データから、0th IFDの部分を抜き出してみました。

0B 00 0F 01 02 00 05 00 00 00 92 00 00 00 10 01
02 00 07 00 00 00 98 00 00 00 12 01 03 00 01 00 
00 00 01 00 00 00 1A 01 05 00 01 00 00 00 A0 00 
00 00 1B 01 05 00 01 00 00 00 A8 00 00 00 28 01 
03 00 01 00 00 00 02 00 00 00 31 01 02 00 14 00 
00 00 B0 00 00 00 32 01 02 00 14 00 00 00 C4 00 
00 00 13 02 03 00 01 00 00 00 01 00 00 00 69 87 
04 00 01 00 00 00 D8 00 00 00 25 88 04 00 01 00 
00 00 2C 52 32 52 00 00

 
こちらのテーブルを頼りに、まずは先頭の2バイトを抜き出します。
「0B 00」の部分は、0th IFDに何個のタグが含まれているかを表す部分です。
このデータのバイトオーダーはリトルエンディアンなので、人間が読む場合は、0x000Bとなります。
つまり、11ですね。
なので、0th IFDには、11個のタグが含まれている、という事になります。

1つのタグは12バイトの固まりなので、12*11=132バイトがタグデータです。

タグを固まりごとに抜き出してみましょう。

0F 01 02 00 05 00 00 00 92 00 00 00 
10 01 02 00 07 00 00 00 98 00 00 00 
12 01 03 00 01 00 00 00 01 00 00 00 
1A 01 05 00 01 00 00 00 A0 00 00 00 
1B 01 05 00 01 00 00 00 A8 00 00 00 
28 01 03 00 01 00 00 00 02 00 00 00 
31 01 02 00 14 00 00 00 B0 00 00 00 
32 01 02 00 14 00 00 00 C4 00 00 00 
13 02 03 00 01 00 00 00 01 00 00 00 
69 87 04 00 01 00 00 00 D8 00 00 00 
25 88 04 00 01 00 00 00 2C 52 00 00

 
タグを要素ごとに切り出して、表にしてみます。
ついでに、タグ番号が何を表しているかを仕様書から調べて追記しています。

タグ番号 / タグ名 タイプ カウント 値へのオフセット値
0F 01
メーカー
02 00
ASCII
05 00 00 00
カウント5
92 00 00 00
10 01
モデル
02 00
ASCII
07 00 00 00
カウント7
98 00 00 00
12 01
画像方向
03 00
SHORT
01 00 00 00
カウント1
01 00 00 00
1A 01
画像の幅の解像度
05 00
RATIONAL
01 00 00 00
カウント1
A0 00 00 00
1B 01
画像の高さの解像度
05 00
RATIONAL
01 00 00 00
カウント1
A8 00 00 00
28 01
画像の幅と高さの解像度の単位
03 00
SHORT
01 00 00 00
カウント1
02 00 00 00
31 01
ソフトウェア
02 00
ASCII
14 00 00 00
カウント20
B0 00 00 00
32 01
ファイル変更日時
02 00
ASCII
14 00 00 00
カウント20
C4 00 00 00
13 02
YCCの画素構成(YとCの位置)
03 00
SHORT
01 00 00 00
カウント1
01 00 00 00
69 87
Exif IFDへのポインタ
04 00
LONG
01 00 00 00
カウント1
D8 00 00 00
25 88
GPS IFDへのポインタ
04 00
LONG
01 00 00 00
カウント1
2C 52 00 00

実際の値の取り方ですが、これにはパターンがあって、タイプがBYTE、SHORT、LONG、SLONGの場合、カウントが1の場合はオフセット部分に値が入っています。
ASCII、UNDEFINEDの場合も、カウントが4以下の場合はオフセット部分に値が入っています。

それ以外の場合は、ここを基準に、データのアドレスを計算してタイプとカウントの分だけ、データを取得します。
取得した結果が以下のようになります。

タグ名
メーカー 53 6F 6E 79 00
Sony
モデル 53 4F 2D 30 34 48 00
SO-04H
画像方向 1
画像の幅の解像度 48 00 00 00 01 00 00 00
72/1
画像の高さの解像度 48 00 00 00 01 00 00 00
72/1
画像の幅と高さの解像度の単位 2
ソフトウェア 33 35 2E 30 2E 42 2E 32 2E 32 37 32 5F 30 5F 66 36 30 30 00
35.0.B.2.272_0_f600
ファイル変更日時 32 30 31 36 3A 31 30 3A 32 31 20 31 35 3A 32 30 3A 35 34 00
2016:10:21 15:20:54
YCCの画素構成(YとCの位置) 1
Exif IFDへのポインタ D8 00 00 00
GPS IFDへのポインタ 2C 52 00 00
1st IFDへのオフセット値 32 52 00 00

となっています。私の携帯の機種がバレた訳ですが、たかがjpeg画像といえど、この程度の情報は簡単に取り出せるということですね。

では、Exif IFDの場所が分かったところで、本題のExif情報を見てみましょう。

 

Exif情報を読んでみる

手順は、0th IFDと全く同じです。Exif情報部分のバイナリを抜き出してみましょう。

1C 00 
9A 82 05 00 01 00 00 00 2E 02 00 00 
9D 82 05 00 01 00 00 00 36 02 00 00 
27 88 03 00 01 00 00 00 A0 00 00 00 
00 90 07 00 04 00 00 00 30 32 32 30 
03 90 02 00 14 00 00 00 3E 02 00 00 
04 90 02 00 14 00 00 00 52 02 00 00 
01 91 07 00 04 00 00 00 01 02 03 00 
01 92 0A 00 01 00 00 00 66 02 00 00 
04 92 0A 00 01 00 00 00 6E 02 00 00 
07 92 03 00 01 00 00 00 05 00 00 00 
08 92 03 00 01 00 00 00 00 00 00 00 
09 92 03 00 01 00 00 00 10 00 00 00 
0A 92 05 00 01 00 00 00 76 02 00 00 
7C 92 07 00 70 4F 00 00 7E 02 00 00 
90 92 02 00 07 00 00 00 EE 51 00 00 
91 92 02 00 07 00 00 00 F6 51 00 00 
92 92 02 00 07 00 00 00 FE 51 00 00 
00 A0 07 00 04 00 00 00 30 31 30 30 
01 A0 03 00 01 00 00 00 01 00 00 00 
02 A0 04 00 01 00 00 00 60 17 00 00 
03 A0 04 00 01 00 00 00 26 0D 00 00 
05 A0 04 00 01 00 00 00 0E 52 00 00 
01 A4 03 00 01 00 00 00 00 00 00 00 
02 A4 03 00 01 00 00 00 00 00 00 00 
03 A4 03 00 01 00 00 00 00 00 00 00 
04 A4 05 00 01 00 00 00 06 52 00 00 
06 A4 03 00 01 00 00 00 00 00 00 00 
0C A4 03 00 01 00 00 00 00 00 00 00 
00 00 00 00

 

タグ番号 タイプ カウント 値へのオフセット値 / 実データ
9A 82
露出時間
05 00
RATIONAL
01 00 00 00
カウント1
2E 02 00 00
0A 00 00 00 40 01 00 00(10/320)
9D 82
Fナンバー
05 00
RATIONAL
01 00 00 00
カウント1
36 02 00 00
14 00 00 00 0A 00 00 00(20/10)
27 88
撮影感度
03 00
SHORT
01 00 00 00
カウント1
A0 00 00 00
10
00 90
Exifバージョン
07 00
UNDEFINED
04 00 00 00
カウント4
30 32 32 30
0220
03 90
原画像データの生成日時
02 00
ASCII
14 00 00 00
カウント20
3E 02 00 00
32 30 31 36 3A 31 30 3A 32 31 20 31 35 3A 32 30 3A 35 34(2016:10:21 15:20:54)
04 90
ディジタルデジタルデータの作成日時
02 00
ASCII
14 00 00 00
カウント20
52 02 00 00
32 30 31 36 3A 31 30 3A 32 31 20 31 35 3A 32 30 3A 35 34(2016:10:21 15:20:54)
01 91
各コンポーネントの意味
07 00
UNDEFINED
04 00 00 00
カウント4
01 02 03 00
その他(Y、Cb、Cr)
01 92
シャッタースピード
0A 00
SRATIONAL
01 00 00 00
カウント1
66 02 00 00
F4 01 00 00 64 00 00 00(500/100)
04 92
露光補正値
0A 00
SRATIONAL
01 00 00 00
カウント1
6E 02 00 00
00 00 00 00 03 00 00 00(0/3)
07 92
測光方式
03 00
SHORT
01 00 00 00
カウント1
05 00 00 00
5(分割測光)
08 92
光源
03 00
SHORT
01 00 00 00
カウント1
00 00 00 00
0(不明)
09 92
フラッシュ
03 00
SHORT
01 00 00 00
カウント1
10 00 00 00
ストロボ発光
0A 92
レンズ焦点距離
05 00
RATIONAL
01 00 00 00
カウント1
76 02 00 00
A7 01 00 00 64 00 00 00(423/100)
7C 92
メーカーノート
07 00
UNDEFINED
70 4F 00 00
カウント20336
7E 02 00 00
データ量が多いため割愛
90 92
DateTimeのサブセック
02 00
ASCII
07 00 00 00
カウント7
EE 51 00 00
36 38 34 37 37 32(684772)
91 92
DateTimeOriginalのサブセック
02 00
ASCII
07 00 00 00
カウント7
F6 51 00 00
36 38 34 37 37 32(684772)
92 92
DateTimeDegitizedのサブセック
02 00
ASCII
07 00 00 00
カウント7
FE 51 00 00
36 38 34 37 37 32(684772)
00 A0
対応フラッシュピックスバージョン
07 00
UNDEFINED
04 00 00 00
カウント4
30 31 30 30
0100(Flashpix Format Version 1.0)
01 A0
色空間情報
03 00
SHORT
01 00 00 00
カウント1
01 00 00 00
sRGB
02 A0
実効画像幅
04 00
LONG
01 00 00 00
カウント1
60 17 00 00
5984
03 A0
実効画像高さ
04 00
LONG
01 00 00 00
カウント1
26 0D 00 00
3366
05 A0
互換性IFDへのポインタ
04 00
LONG
01 00 00 00
カウント1
0E 52 00 00
0E 52 00 00
01 A4
個別画像処理
03 00
SHORT
01 00 00 00
カウント1
00 00 00 00
0
02 A4
露出モード
03 00
SHORT
01 00 00 00
カウント1
00 00 00 00
0(自動露出)
03 A4
ホワイトバランス
03 00
SHORT
01 00 00 00
カウント1
00 00 00 00
0(ホワイトバランス自動)
04 A4
デジタルズーム倍率
05 00
RATIONAL
01 00 00 00
カウント1
06 52 00 00
64 00 00 00 64 00 00 00(100/100)
06 A4
撮影シーンタイプ
03 00
SHORT
01 00 00 00
カウント1
00 00 00 00
0(標準)
0C A4
被写体距離レンジ
03 00
SHORT
01 00 00 00
カウント1
00 00 00 00
0(不明)

この後、GPS情報と続く訳ですが、GPS情報に至っては、どこで撮ったかすぐに分かってしまう訳です。
旅行先の写真などの場合は、撮った写真を地図上に表示したりすると面白いでしょうが、自宅で撮った写真がそうであると困ります。

Exif情報が無事読めて良かったなという達成感でいっぱいですが、PHPなら、read_exif_data関数で上記のデータは簡単に手に入ります。
手元にバイナリエディタとExif資料しかない場合に、この記事が参考になれば幸いです。

 
以上です。

この記事がお役に立てば【 いいね 】のご協力をお願いいたします!
13
読み込み中...
13 票, 平均: 1.00 / 113
27,729
X facebook はてなブックマーク pocket
【2025.6.30 Amazon Linux 2 サポート終了】Amazon Linux サーバー移行ソリューション

【2025.6.30 Amazon Linux 2 サポート終了】Amazon Linux サーバー移行ソリューション

この記事をかいた人

About the author

萬代陽一

ソーシャルゲームのウェブ API などの開発がメイン業務ですが、ありがたいことにマーケティングなどいろんな仕事をさせてもらえています。
なおビヨンド内での私の肖像権は CC0 扱いになっています。