在进行了一些研究并在这里运行了一堆测试之后,我提出了我的问题的解决方案。
首先,我想澄清一下,我们不是在谈论法医调查。可能有一些方法可以处理 JPG 图像,使标记出现在它们不应该出现的位置,并且不会出现在根据规范必须出现的位置。
我们也不谈论图像身份或相似性。如果您无损地旋转 JPG,您仍然拥有相同的图像信息,但不再是相同的图像。我们也不是在谈论以任何其他方式调整大小、优化或更改的图像。
什么我们正在谈论的是识别简单的重复或 JPG 已重命名或元数据已被修改或删除,但图像本身从未被处理过或以任何方式篡改。
SOS 和 EOI 标记之间的字节散列是唯一识别图像的可靠方法吗?
是的,是的。在合理的范围内,图像扫描数据的具有相同 MD5 校验和的两个文件不可能包含不同的图像,反之亦然。
我检查了使用来自 12 个不同制造商的相机拍摄的示例照片,并编辑/剥离了元数据。实际上,这并不是真正必要的,因为从规范和代码中您知道所有元数据都驻留在单独的块中(这就是为什么您可以在 JPG 中隐藏所有类型的东西)和扫描数据元数据操作永远不会触及,但是是的,到处都是相同的 MD5 校验和。
有什么方法可以快速找到(正确的)SOS 标记?
当然。 JPG规格是一团糟和一种惩罚。在尝试了相当多的代码之后,我发现Nils Haeck 的NativeJPG 是最直接的。
本文改编自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;
省略 SOS 标记后的前 6 个字节?
我决定对 SOS 和 EOI 之间的所有内容进行哈希处理,不包括标记本身。
有没有快速定位尾随 EOI 标记的方法?
没有。但这无关紧要,因为要执行哈希,无论如何您都必须读取每个字节。
这种方法的可靠性如何?
正如我所说,我相信在合理范围内这种方法不会产生误报的可能性实际上是 100%。至于找到正确的图像:NativeJPG 已经存在了 10 多年,你发现很少有人抱怨,如果他们处理图像解码,而不是丢失它。
在我的应用程序中,我提供了在 UserComment 字段中存储原始文件名、EXIF DateTimeDigitized、相机品牌、GPS 坐标和扫描数据的 MD5 散列(完整和前 16 kB)的选项。我非常有信心这将允许以后在大多数情况下识别文件(如果 UserComment 保持不变)。