Проведя некоторые исследования и выполнив несколько тестов, я представлю свое решение для моего вопроса.
Во-первых, я хочу пояснить, что мы не говорим о судебном расследовании. Возможно, есть способы манипулировать изображением JPG таким образом, чтобы маркеры появлялись там, где их не должно быть, и не появлялись там, где должны были бы, согласно спецификациям.
Мы не говорим об идентичности или сходстве изображения. Если вы без потерь поворачиваете JPG, у вас остается та же самая информация об изображении, но больше не идентичное изображение. Мы также не говорим об изображениях, которые были изменены, оптимизированы или изменены любым другим способом.
То, о чем мы говорим, - это определение простых дубликатов или JPG, которые были переименованы или метаданные которых были изменены или удалены, но само изображение никогда не обрабатывалось и не изменялось с любым способом.
Является ли хэш байтов между маркерами SOS и EOI надежным способом однозначной идентификации изображения?
Да, это так. В пределах разумного нет никакого способа, чтобы два файла с одинаковыми контрольными суммами MD5 данных сканирования изображения могли содержать неидентичные изображения и наоборот.
Я изучил образцы фотографий, снятых камерами 12 разных производителей, и отредактировал / удалил метаданные. На самом деле в этом не было необходимости, потому что из спецификаций и кода вы знаете , что все метаданные находятся в отдельных блоках (поэтому вы можете скрыть все виды материалов в JPG), и данные сканирования будут никогда не трогайте операции метаданных, но да, везде одинаковые контрольные суммы MD5.
Есть ли способ быстро найти (правый) маркер SOS?
Определенно. Спецификации JPG - беспорядок и наказание. Попробовав несколько кусков кода, я обнаружил, что NativeJPG от Nils Haeck является наиболее простым.
Это было адаптировано из sdJpegImage :
function FindSOSPos(S: TStream): Cardinal;
var
B, MarkerTag, BytesRead: byte;
Size,W: word;
const
mkNone = 0; mkSOF0 = $c0; mkSOF1 = $c1; mkSOF2 = $c2; mkSOF3 = $c3; mkSOF5 = $c5;
mkSOF6 = $c6; mkSOF7 = $c7; mkSOF9 = $c9; mkSOF10 = $ca; mkSOF11 = $cb; mkSOF13 = $cd;
mkSOF14 = $ce; mkSOF15 = $cf; mkDHT = $c4; mkDAC = $cc; mkSOI = $d8; mkEOI = $d9; mkSOS = $da;
mkDQT = $db; mkDNL = $dc; mkDRI = $dd; mkDHP = $de; mkEXP = $df; mkAPP0 = $e0; mkAPP15 = $ef; mkCOM = $fe;
begin
Repeat
Result := 0;
// Read markers from the stream, until a non $FF is encountered
If S.Read(B, 1) = 0 then
exit;
// Do we have a marker?
if B = $FF then
begin
BytesRead := S.Read(MarkerTag, 1);
while (BytesRead > 0) and (MarkerTag = $FF) do
begin
MarkerTag := mkNone;
BytesRead := S.Read(MarkerTag, 1);
end;
Size := 0;
if MarkerTag in [mkAPP0..mkAPP15, mkDHT, mkDQT, mkDRI,
mkSOF0, mkSOF1, mkSOF2, mkSOF3, mkSOF5, mkSOF6, mkSOF7, mkSOF9, mkSOF10, mkSOF11, mkSOF13, mkSOF14, mkSOF15,
mkCOM, mkDNL] then
begin
// Read length of marker
If S.Read(W, 2) = 2 then
Size := Swap(W) - 2
else exit;
end else
If MarkerTag = mkSOS
then break;
S.Position := S.Position + Size;
end else
begin
// B <> $FF is an error, we try to be flexible
repeat
BytesRead := S.Read(B, 1);
until (BytesRead = 0) or (B = $FF);
if BytesRead = 0 then
exit;
S.Seek(-1, soFromCurrent);
end;
Until (MarkerTag = mkSOS) or (MarkerTag = mkNone);
Result := S.Position;
end;
Пропустить первые 6 байтов после маркера SOS?
Я решил все хэшировать между SOS и EOI, кроме самих маркеров.
Есть ли быстрый способ найти маркер EOI в конце?
Нет. Но это не имеет значения, поскольку для выполнения хэша вы все равно должны прочитать каждый байт.
Насколько надежен этот подход?
Как я уже сказал, я считаю, что в пределах разума вероятность того, что этот подход не даст ложных срабатываний, составляет практически 100%. Что касается поиска нужного изображения: NativeJPG существует уже более 10 лет, и вы найдете очень мало жалоб, если таковые имеются, они касаются декодирования изображения, а не пропускают его.
В моем приложении я могу сохранить исходное имя файла, EXIF DateTimeDigitized, марку камеры, координаты GPS и хеши MD5 данных сканирования (полное и первые 16 кБ) в поле UserComment. Я уверен, что это позволит позже идентифицировать файл при большинстве условий (если пользовательский комментарий остался нетронутым).