✳ OMNIVIEWER HEIC metadata HEIC viewer ← back

Two hidden identifiers in iPhone HEIC metadata

While building a HEIC viewer and metadata editor that runs entirely in the browser, we noticed two UUID-shaped strings hiding inside an iPhone photo’s private Apple MakerNote. They were not the phone’s serial number. They were identifiers for the capture and the photo — fields most metadata viewers never show, and therefore fields a person cannot knowingly remove. We added both to OmniViewer’s identifying-metadata view and to its clean-copy path.

On this page A HEIC viewer for Chrome The two strings What they mean Do they identify the phone? Decoding Apple’s nested IFD Why we treat them as identifying Removing them safely

First, we had to make HEIC work in Chrome

A HEIC is not a JPEG with a different extension. It is a HEIF container — an ISO-BMFF box tree — holding one or more HEVC (H.265) coded images, often arranged as a tile grid. The browser has to solve two separate problems: understand the container, then decode the HEVC pictures inside it.

OmniViewer’s container reader is plain JavaScript running in a Web Worker. It reads the meta box, joins iinf, iloc, iref and iprp, finds the primary image and its tiles, and extracts the VPS/SPS/PPS parameter sets from hvcC. None of that requires decoding a pixel, and none of it uploads the file.

For the preview, the viewer rebuilds each HEIC tile as an HEVC elementary stream. In Chrome, it asks VideoDecoder.isConfigSupported() about the file’s exact hvc1.* profile and uses WebCodecs when the platform decoder accepts it. The WebCodecs specification deliberately exposes browser codec implementations to JavaScript, while allowing support to vary by codec and machine; HEVC’s hvc1.*/hev1.* strings are registered in the WebCodecs codec registry.

const support = await VideoDecoder.isConfigSupported({
  codec: hevcCodecFromHvcC,
  codedWidth: tileWidth,
  codedHeight: tileHeight,
});

if (support.supported) decodeWithWebCodecs(tileStreams);
else decodeWithWasm(tileStreams);

Where that native path is unavailable, OmniViewer falls back to FFmpeg compiled to WebAssembly, again inside a worker. So “JavaScript/WASM HEIC viewer” is accurate, but the distinction matters: Chrome normally takes the JavaScript + WebCodecs path; wasm is the portable fallback. The pixels, EXIF and output all stay in the tab.

The metadata editor made the invisible visible

Once the preview worked, we built the metadata viewer/editor. A HEIC stores EXIF as its own item. Inside that item is the familiar TIFF structure: IFD0, an Exif sub-IFD, a GPS IFD, and — on iPhone photos — an opaque MakerNote field owned by Apple.

At first we did what many readers do: show “Maker note · 1,650 bytes” and move on. But OmniViewer also has a synchronized hex pane. In one real iPhone 12 HEIC, two UUID-shaped ASCII strings appeared immediately before the lens-model text:

........-....-4...-....-............   ← UUID-shaped value
........-....-4...-....-............   ← another UUID-shaped value
Apple
iPhone 12 back dual wide camera 4.2mm f/1.6

Their proximity to the lens text was incidental — TIFF values are pooled behind directory entries — but the strings were real. The next question was which MakerNote entries pointed to them.

The best-known names: capture request and photo identifier

Apple does not publicly document this MakerNote dictionary. The most useful public map is ExifTool’s reverse-engineered Apple tag table. Walking the nested directory gave us these two entries:

Apple tagBest-known nameWhat we show
0x0020ImageCaptureRequestID?Capture request ID ?
0x002BPhotoIdentifierPhoto identifier ?

The question mark is important. ExifTool marks the precise interpretation of 0x0020 as uncertain, and neither label is an Apple API contract. OmniViewer links the same reference from each row instead of presenting a reverse-engineered guess as official fact.

Do these UUIDs identify the phone?

Not by themselves. They are not an IMEI, serial number, UDID or stable hardware identifier. Both values have the shape of randomly generated UUIDv4 identifiers. Comparing several photos taken by the same iPhone model showed a different pair in every capture.

That makes the best-supported interpretation narrower: one value belongs to a capture request and the other to the photo asset. They may survive in copies or derivatives and therefore help correlate two files as versions of the same photo. They do not provide a stable key that groups every photograph from one physical phone.

Photo-specific is still identifying. A value does not need to reveal a phone serial number to matter for privacy. If the same opaque identifier appears in a private original and a public derivative, it can link those two files. GPS coordinates, timestamps and camera serial numbers are more direct risks, but hidden correlation IDs still belong in the list a privacy cleaner shows and removes.

Apple put an IFD inside the MakerNote

The outer EXIF reader sees tag 0x927C, type UNDEFINED. The value begins with Apple iOS\0, carries its own byte-order marker at offset 12, and starts a TIFF-like directory at offset 14. Unlike a normal TIFF header, its value offsets are relative to the beginning of the MakerNote:

+0   "Apple iOS\0"
+12  "MM"                    big-endian marker
+14  entry count
+16  first 12-byte IFD entry
     tag | type | count | value-or-offset

0x0020  ASCII  37  → "........-....-4...-....-............\0"
0x002B  ASCII  37  → "........-....-4...-....-............\0"

The decoder is intentionally conservative. It requires the Apple signature, honours either II or MM, bounds-checks the directory and every relative offset, recognizes only the two tags we can label, and emits a row only when the value is actually a UUID. An unknown or malformed MakerNote remains an opaque byte count rather than becoming a confident wrong answer.

if (signature === "Apple iOS\0" && (order === "II" || order === "MM")) {
  for (const entry of makerNoteIfd) {
    if (entry.tag === 0x0020) emit("ImageCaptureRequestID", readUuid(entry));
    if (entry.tag === 0x002b) emit("PhotoIdentifier", readUuid(entry));
  }
}

Each emitted field keeps the byte span of both its directory record and its pointed-to value. That is why clicking the row can highlight the exact 36 characters in the hex pane instead of merely claiming they exist.

A viewer should show what its cleaner removes

There is a product principle behind this small parser: a metadata cleaner should not hide fields merely because they are proprietary. If the UI says “clear identifying metadata”, the user deserves to see the location, camera, dates, editor history and opaque identifiers covered by that action before downloading the result.

We therefore classify both fields as Device / Identifying. They appear in the curated privacy card and in the exhaustive All Metadata table, carry their Apple MakerNote scope and tag number, link to the reverse-engineered source, and remain read-only. Editing a UUID in place would pretend we know lifecycle rules Apple has never published; removal is the honest operation.

Removing the identifiers without re-encoding the photo

HEIC makes fine-grained deletion awkward. EXIF is one item with offsets throughout its TIFF directories; removing two strings while preserving every other MakerNote entry would require rewriting private structures we do not own. OmniViewer’s clean-copy operation instead removes the containing EXIF item as a privacy unit.

More precisely, it overwrites every byte extent belonging to the EXIF and XMP items with zeros. It does not shorten the file, move a box, invalidate an iloc offset, or touch an HEVC tile. The output has the same length and the coded image bytes are identical:

original: [ boxes ][ EXIF with UUIDs ][ XMP ][ HEVC tiles ]
cleaned:  [ boxes ][ 00 00 00 ... 00 ][ 00 ][ HEVC tiles ]
                                           └─ byte-for-byte unchanged

The tests build a real HEIC container with synthetic UUIDs in the same nested Apple structure, assert that both appear as identifying metadata, click Clear identifying, download the result, and assert that neither literal UUID exists anywhere in the cleaned file. They also re-parse the output and verify that the EXIF/XMP entries are gone while the file length and coded data remain unchanged.

The practical result. OmniViewer now shows two pieces of iPhone metadata that used to be visible only as suspicious text in a hex dump. They probably identify the capture and photo, not the physical phone. Because their exact semantics are private and their correlation value is real, the viewer labels them cautiously and the cleaner removes them completely.

Inspect your own HEIC locally

Open HEIC metadata → Drop an iPhone photo, inspect every EXIF/XMP field and download a clean copy. No upload. Open the HEIC viewer → Preview in Chrome with WebCodecs or the wasm fallback, inspect items, metadata, hex and stats. Inside a HEIC → The companion article: boxes, items, HEVC tiles, EXIF extents and zero-fill cleaning.