Saltar al contenido

Referencia de firmas de archivo (magic bytes)

Las firmas (magic bytes) que ocupan los primeros bytes de un archivo. Útiles para identificar tipos y para implementar validación.

Qué son los magic bytes

La mayoría de los formatos empiezan con una secuencia de bytes fija, conocida como magic bytes o firma de archivo. El sistema operativo y las aplicaciones la leen para decidir qué tipo de archivo tienen delante. Una extensión se cambia en un segundo; los magic bytes forman parte del propio binario, y por eso son una base fiable para validar.

Tanto el comando file de Linux como la clase finfo de PHP determinan así el tipo MIME.

Magic bytes de formatos comunes (primeros N bytes) PNG JPEG GIF PDF ZIP WebP MP4 89 50 4E 47 .PNG FF D8 FF SOI marker 47 49 46 38 "GIF8" 25 50 44 46 "%PDF" 50 4B 03 04 "PK\x03\x04" 52 49 46 46 "RIFF" + WEBP at offset 8 66 74 79 70 "ftyp" at offset 4
Diagrama: bytes iniciales (magic bytes) de formatos comunes

Imágenes

FormatoExtensiónMagic bytes (hex)ASCIIDesplazamiento
PNG.png89 50 4E 47 0D 0A 1A 0A‰PNG\r\n\x1a\n0
JPEG.jpg/.jpegFF D8 FF0
GIF87a.gif47 49 46 38 37 61GIF87a0
GIF89a.gif47 49 46 38 39 61GIF89a0
WebP.webp52 49 46 46 ?? ?? ?? ?? 57 45 42 50RIFF????WEBP0
BMP.bmp42 4DBM0
TIFF (little-endian).tiff/.tif49 49 2A 00II*\00
TIFF (big-endian).tiff/.tif4D 4D 00 2AMM\0*0
AVIF / HEIF.avif/.heif66 74 79 70ftyp4
ICO.ico00 00 01 000
SVG.svg<?xml o <svgTexto0 (sin BOM)

Documentos y archivos comprimidos

FormatoExtensiónMagic bytes (hex)Notas
PDF.pdf25 50 44 46 2D%PDF-
ZIP.zip50 4B 03 04PK\x03\x04
ZIP (archivo vacío).zip50 4B 05 06
GZIP.gz1F 8B
7-Zip.7z37 7A BC AF 27 1C7z\xBC\xAF'\x1C
RAR4.rar52 61 72 21 1A 07 00Rar!\x1A\x07\x00
RAR5.rar52 61 72 21 1A 07 01 00
TAR.tar75 73 74 61 72ustar (desplazamiento 257)
DOCX / XLSX / PPTX.docx y similares50 4B 03 04Basado en ZIP; se distingue por la extensión
XLS (formato antiguo).xlsD0 CF 11 E0 A1 B1 1A E1Contenedor OLE2
DOC (formato antiguo).docD0 CF 11 E0 A1 B1 1A E1Contenedor OLE2

Formatos de texto y datos

FormatoExtensiónMagic bytes / BOMNotas
UTF-8 con BOM.txt, .csv y similaresEF BB BFBOM(Byte Order Mark)
UTF-16 LE BOM.txt y similaresFF FELittle-endian
UTF-16 BE BOM.txt y similaresFE FFBig-endian
UTF-32 LE BOM.txt y similaresFF FE 00 00
JSON (habitual).json7B o 5B{ o [
CSV (habitual).csvTexto (sin BOM)Sin firma
XML.xml3C 3F 78 6D 6C<?xml

Audio y vídeo

FormatoExtensiónMagic bytes (hex)Notas
MP3.mp3FF FB / FF F3 / FF F2O una etiqueta ID3: 49 44 33
MP4 / M4A / M4V.mp4 y similares66 74 79 70ftyp (desplazamiento 4)
WAV.wav52 49 46 46 ?? ?? ?? ?? 57 41 56 45RIFF????WAVE
OGG.ogg/.ogv4F 67 67 53OggS
FLAC.flac66 4C 61 43fLaC
AVI.avi52 49 46 46 ?? ?? ?? ?? 41 56 49 20RIFF????AVI
MKV / WebM.mkv/.webm1A 45 DF A3Cabecera EBML

Ejecutables y otros

FormatoExtensiónMagic bytes (hex)Notas
ELF (ejecutable de Linux)ninguna / .elf7F 45 4C 46\x7FELF
PE (ejecutable de Windows).exe/.dll4D 5AMZ
Mach-O (ejecutable de macOS)ningunaCF FA ED FE64bit
Script PHP.php3C 3F 70 68 70<?php
SQLite.sqlite/.db53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00SQLite format 3\0
ISO.iso43 44 30 30 31Desplazamiento 32769: CD001

Validar magic bytes en PHP

 ['offset' => 0, 'bytes' => "\x89PNG\r\n\x1a\n", 'length' => 8],
        'jpg'  => ['offset' => 0, 'bytes' => "\xFF\xD8\xFF",       'length' => 3],
        'gif'  => ['offset' => 0, 'bytes' => "GIF8",               'length' => 4],
        'pdf'  => ['offset' => 0, 'bytes' => "%PDF-",              'length' => 5],
        'zip'  => ['offset' => 0, 'bytes' => "PK\x03\x04",         'length' => 4],
        'exe'  => ['offset' => 0, 'bytes' => "MZ",                 'length' => 2],
    ];

    if (!isset($signatures[$expectedType])) {
        throw new \InvalidArgumentException("Tipo de archivo desconocido: $expectedType");
    }

    $sig = $signatures[$expectedType];
    $handle = fopen($filePath, 'rb');
    if ($sig['offset'] > 0) fseek($handle, $sig['offset']);
    $header = fread($handle, $sig['length']);
    fclose($handle);

    if (substr($header, 0, $sig['length']) !== $sig['bytes']) {
        throw new \RuntimeException("Los magic bytes no coinciden (se esperaba: $expectedType)");
    }
}

// Ejemplo de uso
validateMagicBytes($_FILES['upload']['tmp_name'], 'png');
echo "Es un archivo PNG legítimo";

Recursos relacionados

❓ Preguntas frecuentes

¿Por qué no basta con comprobar la extensión?
La extensión es solo parte del nombre y un atacante puede cambiarla a voluntad: renombra shell.php a shell.jpg y la comprobación de extensión pasa. Los magic bytes miran el inicio real de los datos, así que renombrar no los engaña. Aun así son débiles frente a archivos políglotas que falsifican los primeros bytes, de modo que contrasta extensión, tipo MIME y magic bytes a la vez.
¿Cuántos bytes necesito para identificar PNG y JPEG?
PNG necesita ocho bytes (89 50 4E 47 0D 0A 1A 0A) y JPEG solo los tres primeros (FF D8 FF). En la práctica, leer unos doce bytes cubre de una vez todos los formatos habituales, incluidos ZIP (50 4B 03 04) y PDF (25 50 44 46). No hace falta cargar el archivo entero en memoria.
Los documentos de Office y los APK se detectan como ZIP.
Esa detección es correcta. docx, xlsx, pptx, APK, JAR y EPUB son todos contenedores ZIP y todos empiezan por 50 4B 03 04. Distinguirlos exige abrir el archivo y mirar dentro: [Content_Types].xml para docx, AndroidManifest.xml para un APK. No puedes permitir docx y rechazar APK solo con los magic bytes.