iOS 写真書き出し時の GPS メタデータ削除を検証する

Security ·約 10 分

iOS 写真書き出し時の GPS メタデータ削除を検証する

写真の共有画面に撮影場所が表示されなくても、書き出されたファイルには座標が残っている可能性があります。iOS の画像書き出し機能を検証する際に確認すべきなのは、アプリ内のプレビューではなく、受け取る側に最終的に渡るファイルです。GPS 情報を含むテスト写真をクラウド Mac 上に用意すれば、このプライバシー要件を繰り返し実行できるファイル検査に落とし込めます。

まず書き出しの境界を決める

一般的な処理の流れは、写真の選択、編集またはリサイズ、一時ファイルの生成、共有処理への受け渡しです。どの段階でもメタデータの扱いが変わる可能性があります。写真ピッカーが返した画像だけを調べても、最終ファイルがクリーンになったとは証明できません。共有前のサムネイルだけを調べるのも不十分です。

テスト用画像はチームで作成し、元ファイルに GPS 辞書が実際に含まれていることを確認します。実際のユーザーの写真を継続的インテグレーションのサンプルに使ったり、座標入りのテスト用画像を公開リポジトリに置いたりしないでください。アクセスを制限したテストアセット用ディレクトリに保管し、想定する形式とピクセル寸法を記録できます。テスト後は生成したファイルを削除します。

この記事の合格条件は「最終ファイルに GPS 辞書がないこと」であり、「書き出しファイルが元画像とバイト単位で一致すること」ではありません。再エンコードすれば、通常はファイルのハッシュが変わります。

ImageIO でファイルを生成し、再検査する

以下の Swift スクリプトは入力画像を読み込んでデコードし、元ファイルと同じ形式で再エンコードします。明示的に引き継ぐのは向きだけで、入力ファイルのメタデータ辞書全体は出力側に渡しません。その後、出力ファイルを開き直し、GPS 辞書、寸法、向き、形式を確認します。まず隔離されたディレクトリに redact.swift として保存してください。

import Foundation
import ImageIO
import CoreGraphics

guard CommandLine.arguments.count == 3 else {
    fatalError("Usage: redact input output")
}

let input = URL(fileURLWithPath: CommandLine.arguments[1])
let output = URL(fileURLWithPath: CommandLine.arguments[2])
guard input.standardizedFileURL != output.standardizedFileURL else {
    fatalError("Input and output must differ")
}
guard let source = CGImageSourceCreateWithURL(input as CFURL, nil),
      let type = CGImageSourceGetType(source),
      let image = CGImageSourceCreateImageAtIndex(source, 0, nil),
      let original = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
        as? [CFString: Any],
      let destination = CGImageDestinationCreateWithURL(
        output as CFURL, type, 1, nil
      ) else {
    fatalError("Cannot read input or create output")
}

let orientation = original[kCGImagePropertyOrientation] ?? 1
let options: [CFString: Any] = [
    kCGImagePropertyOrientation: orientation
]
CGImageDestinationAddImage(destination, image, options as CFDictionary)
guard CGImageDestinationFinalize(destination),
      let result = CGImageSourceCreateWithURL(output as CFURL, nil),
      let finalImage = CGImageSourceCreateImageAtIndex(result, 0, nil),
      let metadata = CGImageSourceCopyPropertiesAtIndex(result, 0, nil)
        as? [CFString: Any] else {
    fatalError("Cannot verify output")
}

guard metadata[kCGImagePropertyGPSDictionary] == nil,
      CGImageSourceGetType(result) == type,
      finalImage.width == image.width,
      finalImage.height == image.height,
      String(describing: metadata[kCGImagePropertyOrientation] ?? 1)
        == String(describing: orientation) else {
    fatalError("Export verification failed")
}
print("GPS removed; format, dimensions and orientation checked")

クラウド Mac のターミナルで実行します。

swiftc -framework Foundation -framework ImageIO \
  -framework CoreGraphics redact.swift -o redact
./redact fixtures/with-gps.jpg output/without-gps.jpg

先に output ディレクトリを作成し、例の入力ファイルを GPS 辞書が含まれると確認済みの JPEG に置き換えてください。正常に実行できても、その回の出力が検査に合格したことしか示しません。本番アプリに切り抜き、圧縮、共有拡張機能を通る経路があるなら、それぞれの経路で生成された最終ファイルを検査します。スクリプトをアプリの実際の書き出し処理の代わりにしてはいけません。

不合格になるケースも加える

「クリーンにしたファイルが合格する」だけでは、判定が機能していると証明できません。同じ読み取り処理で入力のテスト用画像を調べ、GPS 辞書が検出されることを確認します。次に出力を調べ、辞書が存在しないことを確認します。入力に最初から座標がなければ、両方が合格しても判別力のないテストです。ログへの漏えいを防ぐため、座標値やメタデータ全体を出力しないでください。

クリーンにしていないファイルのコピーも不合格ケースとして残し、検査器がそれを拒否することを確認します。コピーするときはファイルのバイト列をそのまま複製し、メタデータを書き換える可能性のある画像ツールを経由しないでください。これにより、削除処理だけでなく、検査器が「読み取り失敗」を「GPS なし」と誤判定しないことも検証できます。

再エンコード後に確認すべきこと

位置情報を削除できても、画像としての機能がすべて正常とは限りません。デコードと再エンコードによって、非可逆圧縮の画質、色の見え方、位置情報以外のメタデータが変わる可能性があります。寸法が同じでも、各ピクセルが一致するとは限りません。回帰テストには異なる入手元の写真を加え、少なくとも縦向きと横向きの写真、異なる向きの指定を網羅します。最終ファイルを目視で確認し、回転、色のずれ、明らかな画質劣化がないか調べてください。

確認項目 合格条件
位置情報 最終ファイルに GPS 辞書が存在しない
画像の寸法 その書き出し経路で想定する幅と高さに一致する
表示の向き 出力ファイルの向きの指定に従って正しく表示される
ファイル形式 受け取る側に想定どおりの形式のファイルが渡る
読み取り失敗 テストを不合格とし、「GPS なし」として扱わない

このスクリプトは入力と同じ寸法を要求するため、リサイズしない書き出し経路に適しています。製品で切り抜きや圧縮を行う場合は、その経路で明確に想定する寸法に合わせてアサーションを変更します。カラープロファイルや特定の著作権フィールドを残す必要がある場合も、それぞれにテストを用意してください。それらを残すために元のメタデータ辞書を丸ごとコピーしてはいけません。

クラウド Mac の受け入れテストに組み込む

テスト用画像、アプリが実際に生成したファイル、検査スクリプトは分けて配置します。パイプラインでは先に書き出しを実行し、最終的な受け渡し先のパスにあるファイルを読み取ります。ファイルがない、デコードできない、メタデータの読み取りに失敗した場合は、いずれも直ちにテストを不合格にします。並列ジョブには別々の出力ディレクトリを使い、前回のファイルが残っていることで誤って合格しないようにします。

最後に手作業でも確認します。アプリから実際に共有または保存を行い、中間キャッシュだけでなく、受け取る側が手にしたファイルを再検査してください。これで「編集後のプレビューはクリーンだが、共有時に元ファイルを再び参照してしまう」といった経路の誤りを見つけられます。合格後は一時的な書き出しファイルを削除します。テストレポートには GPS 辞書の有無、寸法、向き、失敗した段階だけを記録し、座標は記録しません。これなら検査結果を再現でき、調査用ログに削除対象の情報を再び露出させずに済みます。

よくある質問

画面に撮影場所が出ていなければ、書き出した写真も安全ですか?

いいえ。画面表示とファイル書き出しは別の経路です。完成ファイルを読み直し、GPS 辞書が存在しないことを確認してください。

元画像と書き出し画像のハッシュを比較すれば十分ですか?

十分ではありません。再エンコードでファイルのバイト列は変わり得ます。GPS 情報、寸法、向き、表示結果を個別に検査します。

利用期間を選んで申し込む

独占利用できる物理Mac miniで次のステップを検証

VMCommitではM4搭載の物理Mac miniを独占利用できます。日・週・月・四半期単位でレンタル可能です。モデルとロケーションを選ぶと、コンソールでリアルタイムの空き状況を確認できます。

Mac miniを今すぐレンタル