Task 339: .KEY File Format
Task 339: .KEY File Format
The .KEY file format, used by Apple Keynote presentations, is a proprietary compressed ZIP archive. Based on available specifications and reverse-engineered details, the properties intrinsic to its file system include:
- File signature (magic number): 50 4B 03 04 (PK\003\004, indicating a ZIP archive).
- MIME type: application/vnd.apple.keynote.
- Internal ZIP structure with specific directories and files, including:
- Data/ (contains media resources like images, videos, and audio).
- Index/ (contains .iwa files or Index.zip with presentation data; .iwa files are Snappy-compressed Protobuf-based files holding metadata, text, slide definitions, transitions, and animations).
- Metadata/ (contains plist files such as buildVersionHistory.plist for version history, DocumentIdentifier for a unique UUID, and Properties.plist for document properties like author, title, language, and company).
- QuickLook/ (contains Thumbnail.jpg and sometimes Preview.pdf for macOS Quick Look previews).
- Preview files: preview.jpg, preview-micro.jpg, preview-web.jpg (JPEG previews of the presentation).
- Version compatibility: Tied to Keynote versions (e.g., Protobuf definitions change across versions like 10.2 to 11.2, affecting backwards compatibility).
- Compression: Overall ZIP compression, with internal .iwa files using Snappy compression.
- No public official specification; format is proprietary but can be unpacked as ZIP.
Two direct download links for .KEY files:
- http://www.trading-library.net:8051/PDC4S%253A/2018-2/Jason%2520Fladlien%252C%2520Russel%2520Brunson%2520-%2520Webinar%2520Blueprint/Powerpoint-and-Keynote-Template/perfect-webinar-keynote.key
- https://hci.stanford.edu/courses/cs147/2015/au/projects/learning/helplist/source_files/cs147-4.key
Here's an HTML/JavaScript snippet that can be embedded in a Ghost blog post. It allows users to drag and drop a .KEY file, treats it as a ZIP, extracts the key components, and dumps the properties (e.g., file listings, metadata from plists, preview info) to the screen. It uses JSZip for ZIP handling and plist.js for parsing plists (include those libraries via CDN).
Drag and drop a .KEY file here
- Python class for handling .KEY files. It uses zipfile for unpacking, plistlib for plists, and prints properties. For write, it can modify and re-zip (simple example).
import zipfile
import plistlib
import os
from io import BytesIO
class KeyFileHandler:
def __init__(self, filepath):
self.filepath = filepath
self.zip = None
self.properties = {}
def open(self):
self.zip = zipfile.ZipFile(self.filepath, 'r')
self._decode_properties()
def _decode_properties(self):
# Extract file structure
self.properties['file_signature'] = self.zip.read(self.zip.namelist()[0])[:4].hex().upper()
self.properties['structure'] = self.zip.namelist()
# Parse Metadata plists
plists = ['Metadata/buildVersionHistory.plist', 'Metadata/Properties.plist']
for plist_path in plists:
if plist_path in self.zip.namelist():
with self.zip.open(plist_path) as f:
self.properties[plist_path] = plistlib.loads(f.read())
# DocumentIdentifier is text
doc_id_path = 'Metadata/DocumentIdentifier'
if doc_id_path in self.zip.namelist():
with self.zip.open(doc_id_path) as f:
self.properties[doc_id_path] = f.read().decode('utf-8').strip()
# Check previews and index
self.properties['previews'] = [f for f in self.zip.namelist() if 'preview' in f or 'Thumbnail' in f]
self.properties['has_index'] = any('Index' in f for f in self.zip.namelist())
def print_properties(self):
for key, value in self.properties.items():
print(f"{key}: {value}")
def write(self, new_filepath, modifications=None):
# Simple write: copy and apply mods (e.g., dict of path: content)
with zipfile.ZipFile(new_filepath, 'w') as new_zip:
for item in self.zip.infolist():
data = self.zip.read(item.filename)
if modifications and item.filename in modifications:
data = modifications[item.filename]
new_zip.writestr(item, data)
def close(self):
if self.zip:
self.zip.close()
# Usage example:
# handler = KeyFileHandler('example.key')
# handler.open()
# handler.print_properties()
# handler.write('modified.key', {'Metadata/Properties.plist': plistlib.dumps({'new_key': 'value'})})
# handler.close()
- Java class for handling .KEY files. Uses java.util.zip and external plist parser (assume com.dd.plist).
import java.io.*;
import java.util.*;
import java.util.zip.*;
import com.dd.plist.*; // External library for plist parsing, e.g., dd-plist
public class KeyFileHandler {
private String filepath;
private ZipFile zip;
private Map<String, Object> properties = new HashMap<>();
public KeyFileHandler(String filepath) {
this.filepath = filepath;
}
public void open() throws IOException {
zip = new ZipFile(filepath);
decodeProperties();
}
private void decodeProperties() throws IOException {
// File signature (first 4 bytes)
ZipEntry firstEntry = zip.entries().nextElement();
byte[] buffer = new byte[4];
try (InputStream is = zip.getInputStream(firstEntry)) {
is.read(buffer);
}
properties.put("file_signature", bytesToHex(buffer));
// Structure
List<String> structure = new ArrayList<>();
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
structure.add(entries.nextElement().getName());
}
properties.put("structure", structure);
// Parse plists
String[] plists = {"Metadata/buildVersionHistory.plist", "Metadata/Properties.plist"};
for (String plistPath : plists) {
ZipEntry entry = zip.getEntry(plistPath);
if (entry != null) {
try (InputStream is = zip.getInputStream(entry)) {
NSObject obj = PropertyListParser.parse(is);
properties.put(plistPath, obj);
} catch (Exception e) {
// Handle error
}
}
}
// DocumentIdentifier
ZipEntry docIdEntry = zip.getEntry("Metadata/DocumentIdentifier");
if (docIdEntry != null) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(zip.getInputStream(docIdEntry)))) {
properties.put("Metadata/DocumentIdentifier", br.readLine());
}
}
// Previews and index
List<String> previews = new ArrayList<>();
boolean hasIndex = false;
entries = zip.entries();
while (entries.hasMoreElements()) {
String name = entries.nextElement().getName();
if (name.contains("preview") || name.contains("Thumbnail")) {
previews.add(name);
}
if (name.contains("Index")) {
hasIndex = true;
}
}
properties.put("previews", previews);
properties.put("has_index", hasIndex);
}
public void printProperties() {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String newFilepath, Map<String, byte[]> modifications) throws IOException {
try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(newFilepath))) {
Enumeration<? extends ZipEntry> entries = zip.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
byte[] data = inputStreamToBytes(zip.getInputStream(entry));
if (modifications != null && modifications.containsKey(entry.getName())) {
data = modifications.get(entry.getName());
}
ZipEntry newEntry = new ZipEntry(entry.getName());
zos.putNextEntry(newEntry);
zos.write(data);
zos.closeEntry();
}
}
}
public void close() throws IOException {
if (zip != null) {
zip.close();
}
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
return sb.toString().trim();
}
private byte[] inputStreamToBytes(InputStream is) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int nRead;
byte[] data = new byte[16384];
while ((nRead = is.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, nRead);
}
return buffer.toByteArray();
}
// Usage example in main method...
}
- JavaScript class for handling .KEY files (Node.js, uses jszip and plist).
const fs = require('fs');
const JSZip = require('jszip');
const plist = require('plist');
class KeyFileHandler {
constructor(filepath) {
this.filepath = filepath;
this.zip = null;
this.properties = {};
}
async open() {
const data = fs.readFileSync(this.filepath);
this.zip = await JSZip.loadAsync(data);
await this.decodeProperties();
}
async decodeProperties() {
// File signature
const firstFile = Object.keys(this.zip.files)[0];
const firstContent = await this.zip.file(firstFile).async('arraybuffer');
const sig = Buffer.from(firstContent.slice(0, 4)).toString('hex').toUpperCase();
this.properties.file_signature = sig;
// Structure
this.properties.structure = Object.keys(this.zip.files);
// Parse plists
const plists = ['Metadata/buildVersionHistory.plist', 'Metadata/Properties.plist'];
for (const plistPath of plists) {
if (this.zip.file(plistPath)) {
const content = await this.zip.file(plistPath).async('text');
this.properties[plistPath] = plist.parse(content);
}
}
// DocumentIdentifier
const docIdPath = 'Metadata/DocumentIdentifier';
if (this.zip.file(docIdPath)) {
const content = await this.zip.file(docIdPath).async('text');
this.properties[docIdPath] = content.trim();
}
// Previews and index
this.properties.previews = this.properties.structure.filter(f => f.includes('preview') || f.includes('Thumbnail'));
this.properties.has_index = this.properties.structure.some(f => f.includes('Index'));
}
printProperties() {
console.log(this.properties);
}
async write(newFilepath, modifications = {}) {
for (const [path, content] of Object.entries(modifications)) {
this.zip.file(path, content);
}
const buffer = await this.zip.generateAsync({type: 'nodebuffer'});
fs.writeFileSync(newFilepath, buffer);
}
close() {
// No close needed for JSZip
}
}
// Usage:
// const handler = new KeyFileHandler('example.key');
// await handler.open();
// handler.printProperties();
// await handler.write('modified.key', {'Metadata/Properties.plist': plist.build({new_key: 'value'})});
- For C (using C++ for class support), a class to handle .KEY files. Uses miniz for ZIP (assume included), and a simple plist parser (simplified, as full plist parsing in C++ is complex; use external if needed).
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <string>
#include <sstream>
#include "miniz.h" // Assume miniz for ZIP handling
// For plist, simplified parser or use external like tinyxml for XML plists
class KeyFileHandler {
private:
std::string filepath;
mz_zip_archive zip;
std::map<std::string, std::string> properties; // Simplified to string values
public:
KeyFileHandler(const std::string& fp) : filepath(fp) {
memset(&zip, 0, sizeof(zip));
}
~KeyFileHandler() {
close();
}
bool open() {
if (!mz_zip_reader_init_file(&zip, filepath.c_str(), 0)) {
return false;
}
decodeProperties();
return true;
}
void decodeProperties() {
// File signature
void* firstData;
size_t firstSize;
std::string firstFile = mz_zip_reader_get_filename(&zip, 0);
mz_zip_reader_extract_to_mem(&zip, 0, &firstData, &firstSize, 0);
std::string sig;
for (size_t i = 0; i < 4 && i < firstSize; ++i) {
char buf[3];
sprintf(buf, "%02X ", ((unsigned char*)firstData)[i]);
sig += buf;
}
properties["file_signature"] = sig.substr(0, sig.size() - 1);
mz_free(firstData);
// Structure
std::stringstream structure;
for (mz_uint i = 0; i < mz_zip_reader_get_num_files(&zip); ++i) {
structure << mz_zip_reader_get_filename(&zip, i) << " ";
}
properties["structure"] = structure.str();
// Simplified plist parsing (text dump)
std::vector<std::string> plists = {"Metadata/buildVersionHistory.plist", "Metadata/Properties.plist", "Metadata/DocumentIdentifier"};
for (const auto& p : plists) {
mz_uint index;
if (mz_zip_reader_locate_file(&zip, p.c_str(), nullptr, 0, &index)) {
void* data;
size_t size;
mz_zip_reader_extract_to_mem(&zip, index, &data, &size, 0);
properties[p] = std::string((char*)data, size);
mz_free(data);
}
}
// Previews and index
std::stringstream previews;
bool hasIndex = false;
for (mz_uint i = 0; i < mz_zip_reader_get_num_files(&zip); ++i) {
std::string name = mz_zip_reader_get_filename(&zip, i);
if (name.find("preview") != std::string::npos || name.find("Thumbnail") != std::string::npos) {
previews << name << " ";
}
if (name.find("Index") != std::string::npos) {
hasIndex = true;
}
}
properties["previews"] = previews.str();
properties["has_index"] = hasIndex ? "true" : "false";
}
void printProperties() {
for (const auto& prop : properties) {
std::cout << prop.first << ": " << prop.second << std::endl;
}
}
bool write(const std::string& newFilepath, const std::map<std::string, std::string>& modifications) {
mz_zip_archive newZip;
memset(&newZip, 0, sizeof(newZip));
if (!mz_zip_writer_init_file(&newZip, newFilepath.c_str(), 0)) {
return false;
}
for (mz_uint i = 0; i < mz_zip_reader_get_num_files(&zip); ++i) {
std::string name = mz_zip_reader_get_filename(&zip, i);
void* data;
size_t size;
mz_zip_reader_extract_to_mem(&zip, i, &data, &size, 0);
auto it = modifications.find(name);
if (it != modifications.end()) {
data = (void*)it->second.data();
size = it->second.size();
}
mz_zip_writer_add_mem(&newZip, name.c_str(), data, size, MZ_DEFAULT_COMPRESSION);
mz_free(data);
}
mz_zip_writer_finalize_archive(&newZip);
mz_zip_writer_end(&newZip);
return true;
}
void close() {
mz_zip_reader_end(&zip);
}
};
// Usage:
// KeyFileHandler handler("example.key");
// if (handler.open()) {
// handler.printProperties();
// std::map<std::string, std::string> mods = {{"Metadata/Properties.plist", "<plist><dict><key>new</key><string>value</string></dict></plist>"}};
// handler.write("modified.key", mods);
// }