Task 666: .SIG File Format
Task 666: .SIG File Format
File Format Specifications for .SIG
The .SIG file format refers to a detached digital signature file in the OpenPGP standard, as defined in RFC 4880 ("OpenPGP Message Format"). This format is commonly used for verifying the integrity and authenticity of associated data files (e.g., software releases). A .SIG file contains one or more OpenPGP packets, primarily a Signature Packet (tag 2), and may be in binary form or ASCII-armored (though .SIG typically denotes binary). The structure ensures cryptographic verification using public-key algorithms, hashing, and subpacket metadata for details like timestamps and key identifiers. Parsing involves reading the packet header (CTB and length), followed by the signature-specific fields and subpackets.
1. List of All Properties Intrinsic to the File Format
The following properties are inherent to the OpenPGP Signature Packet structure in a .SIG file. They represent the core elements for decoding, verification, and serialization:
- Packet Tag: 1 byte (always 0x02 for Signature Packet).
- Packet Length: Variable (1-5 bytes, encoded per OpenPGP length rules: short form <192, two-byte form 192-223, five-byte form with 0xFF prefix, or partial body lengths).
- Version: 1 byte (0x03 for legacy v3 signatures or 0x04 for modern v4 signatures).
- Signature Type: 1 byte (e.g., 0x00 for binary document signature, 0x01 for text document, 0x02 for standalone).
- Public-Key Algorithm: 1 byte (e.g., 0x01 for RSA, 0x11 for DSA, 0x12 for ElGamal, 0x16 for ECDSA).
- Hash Algorithm: 1 byte (e.g., 0x02 for SHA-1, 0x08 for SHA-256, 0x0A for SHA-512).
- Creation Time (v3: 4 bytes Unix timestamp; v4: subpacket type 0x02, 4 bytes).
- Key ID (v3: 8 bytes; v4: subpacket type 0x10, 8 bytes, or full issuer fingerprint in subpacket type 0x11, 20 bytes).
- Expiration Time (v4 subpacket type 0x09, 4 bytes relative to creation time).
- Revocable (v4 subpacket type 0x07, 1 byte flag).
- Hashed Subpacket Length: 2 bytes (total length of hashed area in v4).
- Hashed Subpackets: Variable-length array of subpackets (each: 1-2 byte scalar length + type byte [critical flag in bit 7] + data; types include 0x01 exportable, 0x03 trust signature, 0x04-0x06 regex/revocable, 0x0B preferred symmetric algos [list], 0x0C revocation key, 0x0D notation data, 0x0E preferred hash algos, 0x0F preferred compression, 0x10 issuer, 0x11 issuer fingerprint, 0x12 key server prefs, 0x13 preferred key server, 0x14 primary user ID, 0x15 policy URL, 0x16 key flags, 0x17 signer's user ID, 0x18 revocation reason, 0x19 features, 0x1A signature target, 0x1B embedded signature).
- Unhashed Subpacket Length: 2 bytes (total length of unhashed area in v4).
- Unhashed Subpackets: Variable-length array (same structure as hashed subpackets).
- Hash Value (Left): 2 bytes (first two octets of the signed hash for quick verification).
- Hash Value (Right): 2 bytes (last two octets of the signed hash).
- Signature Value: Variable (algorithm-specific multi-precision integers [MPIs]; e.g., RSA: 1 MPI [s]; DSA: 2 MPIs [r, s]; ECDSA: 1 MPI [r || s concatenated]).
These properties ensure the signature's cryptographic validity, metadata integrity, and interoperability.
2. Two Direct Download Links for .SIG Files
- https://github.com/HandBrake/HandBrake/releases/download/1.8.1/HandBrake-1.8.1-win64.exe.sig (PGP detached signature for HandBrake Windows release).
- https://github.com/veracrypt/VeraCrypt/releases/download/v1.25.9/VeraCrypt_1.25.9_Setup_x86.exe.sig (PGP detached signature for VeraCrypt Windows setup).
3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .SIG Parsing
The following is a self-contained HTML snippet embeddable in a Ghost blog post (place within a raw HTML card). It enables drag-and-drop of a .SIG file (assumes binary format), parses the OpenPGP Signature Packet, extracts and displays all listed properties on-screen, and provides a "Write" button to download a serialized copy of the parsed file.
This code handles basic parsing (assumes single packet, definite length, v4 preferred; extends easily for full subpacket details or MPIs). Properties are dumped as JSON for readability.
4. Python Class for .SIG Handling
The following Python class opens a .SIG file, decodes the properties, prints them to console, and writes a serialized copy.
import struct
import sys
import os
class SigParser:
def __init__(self, filename):
with open(filename, 'rb') as f:
self.data = f.read()
self.pos = 0
def read_byte(self):
b = self.data[self.pos]
self.pos += 1
return b
def read_length(self):
first = self.read_byte()
if first < 192:
return first
elif first < 224:
second = self.read_byte()
return ((first - 192) * 256 + second) + 192
else:
# 5-byte
self.read_byte() # 0xFF
return struct.unpack('<I', self.data[self.pos:self.pos+4])[0]
self.pos += 4
def parse(self):
props = {}
ctb = self.read_byte()
props['packetTag'] = ctb & 0x3F
if props['packetTag'] != 2:
raise ValueError("Not a signature packet")
props['packetLength'] = self.read_length()
props['version'] = self.read_byte()
props['signatureType'] = self.read_byte()
props['pubKeyAlgo'] = self.read_byte()
props['hashAlgo'] = self.read_byte()
if props['version'] == 3:
props['creationTime'] = struct.unpack('<I', self.data[self.pos:self.pos+4])[0]
self.pos += 4
props['keyId'] = ''.join(f'{b:02x}' for b in self.data[self.pos:self.pos+8])
self.pos += 8
else:
hashed_len = struct.unpack('<H', self.data[self.pos:self.pos+2])[0]
self.pos += 2
props['hashedSubpacketLength'] = hashed_len
hashed_subs = []
sub_pos = self.pos
while sub_pos < self.pos + hashed_len:
sub_len_first = self.data[sub_pos]
sub_pos += 1
if sub_len_first > 191:
sub_len = ((sub_len_first - 192) * 256 + self.data[sub_pos]) + 192
sub_pos += 1
else:
sub_len = sub_len_first
sub_type = self.data[sub_pos] & 0x7F
hashed_subs.append({'type': sub_type, 'length': sub_len})
sub_pos += sub_len
props['hashedSubpackets'] = hashed_subs
self.pos += hashed_len
unhashed_len = struct.unpack('<H', self.data[self.pos:self.pos+2])[0]
self.pos += 2
props['unhashedSubpacketLength'] = unhashed_len
unhashed_subs = []
sub_pos = self.pos
while sub_pos < self.pos + unhashed_len:
sub_len_first = self.data[sub_pos]
sub_pos += 1
if sub_len_first > 191:
sub_len = ((sub_len_first - 192) * 256 + self.data[sub_pos]) + 192
sub_pos += 1
else:
sub_len = sub_len_first
sub_type = self.data[sub_pos] & 0x7F
unhashed_subs.append({'type': sub_type, 'length': sub_len})
sub_pos += sub_len
props['unhashedSubpackets'] = unhashed_subs
self.pos += unhashed_len
props['hashLeft'] = ''.join(f'{b:02x}' for b in self.data[self.pos:self.pos+2])
self.pos += 2
props['hashRight'] = ''.join(f'{b:02x}' for b in self.data[self.pos:self.pos+2])
self.pos += 2
sig_end = self.pos + (props['packetLength'] - (self.pos - 3)) # Approx
props['signatureValue'] = ' '.join(f'{b:02x}' for b in self.data[self.pos:sig_end])
return props
def print_properties(self):
props = self.parse()
for k, v in props.items():
print(f"{k}: {v}")
def write(self, output_filename):
with open(output_filename, 'wb') as f:
f.write(self.data)
print(f"Written to {output_filename}")
# Usage
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python sig_parser.py <file.sig>")
sys.exit(1)
parser = SigParser(sys.argv[1])
parser.print_properties()
parser.write("output.sig")
5. Java Class for .SIG Handling
The following Java class opens a .SIG file, decodes the properties, prints them to console, and writes a serialized copy. Compile and run with javac SigParser.java && java SigParser <file.sig>.
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.*;
public class SigParser {
private byte[] data;
private int pos = 0;
public SigParser(String filename) throws IOException {
FileInputStream fis = new FileInputStream(filename);
data = fis.readAllBytes();
fis.close();
}
private int readByte() {
return data[pos++] & 0xFF;
}
private int readLength() throws IOException {
int first = readByte();
if (first < 192) {
return first;
} else if (first < 224) {
int second = readByte();
return ((first - 192) * 256 + second) + 192;
} else {
readByte(); // 0xFF
ByteBuffer bb = ByteBuffer.wrap(data, pos, 4).order(ByteOrder.LITTLE_ENDIAN);
pos += 4;
return bb.getInt();
}
}
public Map<String, Object> parse() throws IOException {
Map<String, Object> props = new HashMap<>();
int ctb = readByte();
props.put("packetTag", ctb & 0x3F);
if ((int) props.get("packetTag") != 2) throw new IOException("Not a signature packet");
props.put("packetLength", readLength());
props.put("version", readByte());
props.put("signatureType", readByte());
props.put("pubKeyAlgo", readByte());
props.put("hashAlgo", readByte());
int version = (int) props.get("version");
if (version == 3) {
ByteBuffer bb = ByteBuffer.wrap(data, pos, 4).order(ByteOrder.LITTLE_ENDIAN);
props.put("creationTime", bb.getInt());
pos += 4;
StringBuilder keyId = new StringBuilder();
for (int i = 0; i < 8; i++) {
keyId.append(String.format("%02x", data[pos++] & 0xFF));
}
props.put("keyId", keyId.toString());
} else {
ByteBuffer bb = ByteBuffer.wrap(data, pos, 2).order(ByteOrder.LITTLE_ENDIAN);
int hashedLen = bb.getShort();
pos += 2;
props.put("hashedSubpacketLength", hashedLen);
List<Map<String, Integer>> hashedSubs = new ArrayList<>();
int subPos = pos;
while (subPos < pos + hashedLen) {
int subLenFirst = data[subPos++];
int subLen;
if (subLenFirst > 191) {
subLen = ((subLenFirst - 192) * 256 + data[subPos++]) + 192;
} else {
subLen = subLenFirst;
}
int subType = data[subPos] & 0x7F;
hashedSubs.add(Map.of("type", subType, "length", subLen));
subPos += subLen;
}
props.put("hashedSubpackets", hashedSubs);
pos += hashedLen;
bb = ByteBuffer.wrap(data, pos, 2).order(ByteOrder.LITTLE_ENDIAN);
int unhashedLen = bb.getShort();
pos += 2;
props.put("unhashedSubpacketLength", unhashedLen);
List<Map<String, Integer>> unhashedSubs = new ArrayList<>();
subPos = pos;
while (subPos < pos + unhashedLen) {
int subLenFirst = data[subPos++];
int subLen;
if (subLenFirst > 191) {
subLen = ((subLenFirst - 192) * 256 + data[subPos++]) + 192;
} else {
subLen = subLenFirst;
}
int subType = data[subPos] & 0x7F;
unhashedSubs.add(Map.of("type", subType, "length", subLen));
subPos += subLen;
}
props.put("unhashedSubpackets", unhashedSubs);
pos += unhashedLen;
}
StringBuilder hashLeft = new StringBuilder();
for (int i = 0; i < 2; i++) hashLeft.append(String.format("%02x", data[pos++] & 0xFF));
props.put("hashLeft", hashLeft.toString());
StringBuilder hashRight = new StringBuilder();
for (int i = 0; i < 2; i++) hashRight.append(String.format("%02x", data[pos++] & 0xFF));
props.put("hashRight", hashRight.toString());
int sigEnd = pos + ((int) props.get("packetLength") - (pos - 3));
StringBuilder sigVal = new StringBuilder();
for (int i = pos; i < sigEnd; i++) {
sigVal.append(String.format("%02x ", data[i] & 0xFF));
}
props.put("signatureValue", sigVal.toString().trim());
return props;
}
public void printProperties() throws IOException {
Map<String, Object> props = parse();
for (Map.Entry<String, Object> entry : props.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
public void write(String outputFilename) throws IOException {
try (FileOutputStream fos = new FileOutputStream(outputFilename)) {
fos.write(data);
}
System.out.println("Written to " + outputFilename);
}
public static void main(String[] args) throws IOException {
if (args.length < 1) {
System.err.println("Usage: java SigParser <file.sig>");
System.exit(1);
}
SigParser parser = new SigParser(args[0]);
parser.printProperties();
parser.write("output.sig");
}
}
6. JavaScript Class for .SIG Handling (Node.js)
The following Node.js class opens a .SIG file, decodes the properties, prints them to console, and writes a serialized copy. Run with node sig_parser.js <file.sig> (requires fs module).
const fs = require('fs');
class SigParser {
constructor(filename) {
this.data = fs.readFileSync(filename);
this.pos = 0;
}
readByte() {
const b = this.data[this.pos];
this.pos++;
return b;
}
readLength() {
let first = this.readByte();
if (first < 192) {
return first;
} else if (first < 224) {
let second = this.readByte();
return ((first - 192) * 256 + second) + 192;
} else {
this.readByte(); // 0xFF
const view = new DataView(this.data.buffer, this.pos, 4);
const len = view.getUint32(this.pos, true);
this.pos += 4;
return len;
}
}
parse() {
const props = {};
let ctb = this.readByte();
props.packetTag = ctb & 0x3F;
if (props.packetTag !== 2) throw new Error('Not a signature packet');
props.packetLength = this.readLength();
props.version = this.readByte();
props.signatureType = this.readByte();
props.pubKeyAlgo = this.readByte();
props.hashAlgo = this.readByte();
if (props.version === 3) {
const view = new DataView(this.data.buffer, this.pos, 4);
props.creationTime = view.getUint32(this.pos, true);
this.pos += 4;
let keyId = '';
for (let i = 0; i < 8; i++) {
keyId += this.data[this.pos++].toString(16).padStart(2, '0');
}
props.keyId = keyId;
} else {
const view = new DataView(this.data.buffer, this.pos, 2);
let hashedLen = view.getUint16(this.pos, true);
this.pos += 2;
props.hashedSubpacketLength = hashedLen;
const hashedSubs = [];
let subPos = this.pos;
while (subPos < this.pos + hashedLen) {
let subLenFirst = this.data[subPos++];
let subLen;
if (subLenFirst > 191) {
subLen = ((subLenFirst - 192) * 256 + this.data[subPos++]) + 192;
} else {
subLen = subLenFirst;
}
let subType = this.data[subPos] & 0x7F;
hashedSubs.push({type: subType, length: subLen});
subPos += subLen;
}
props.hashedSubpackets = hashedSubs;
this.pos += hashedLen;
view = new DataView(this.data.buffer, this.pos, 2);
let unhashedLen = view.getUint16(this.pos, true);
this.pos += 2;
props.unhashedSubpacketLength = unhashedLen;
const unhashedSubs = [];
subPos = this.pos;
while (subPos < this.pos + unhashedLen) {
let subLenFirst = this.data[subPos++];
let subLen;
if (subLenFirst > 191) {
subLen = ((subLenFirst - 192) * 256 + this.data[subPos++]) + 192;
} else {
subLen = subLenFirst;
}
let subType = this.data[subPos] & 0x7F;
unhashedSubs.push({type: subType, length: subLen});
subPos += subLen;
}
props.unhashedSubpackets = unhashedSubs;
this.pos += unhashedLen;
}
let hashLeft = '';
for (let i = 0; i < 2; i++) hashLeft += this.data[this.pos++].toString(16).padStart(2, '0');
props.hashLeft = hashLeft;
let hashRight = '';
for (let i = 0; i < 2; i++) hashRight += this.data[this.pos++].toString(16).padStart(2, '0');
props.hashRight = hashRight;
let sigEnd = this.pos + (props.packetLength - (this.pos - 3));
let sigVal = '';
for (let i = this.pos; i < sigEnd; i++) {
sigVal += this.data[i].toString(16).padStart(2, '0') + ' ';
}
props.signatureValue = sigVal.trim();
return props;
}
printProperties() {
const props = this.parse();
for (const [k, v] of Object.entries(props)) {
console.log(`${k}: ${v}`);
}
}
write(outputFilename) {
fs.writeFileSync(outputFilename, this.data);
console.log(`Written to ${outputFilename}`);
}
}
// Usage
const args = process.argv.slice(2);
if (args.length < 1) {
console.error('Usage: node sig_parser.js <file.sig>');
process.exit(1);
}
const parser = new SigParser(args[0]);
parser.printProperties();
parser.write('output.sig');
7. C Class (Struct) for .SIG Handling
The following C code defines a struct-based "class" for .SIG handling, opens the file, decodes the properties, prints them to console, and writes a serialized copy. Compile with gcc sig_parser.c -o sig_parser && ./sig_parser <file.sig>.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
typedef struct {
unsigned char *data;
size_t pos;
size_t len;
} SigParser;
SigParser *sig_parser_new(const char *filename) {
FILE *f = fopen(filename, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
size_t size = ftell(f);
fseek(f, 0, SEEK_SET);
unsigned char *buf = malloc(size);
fread(buf, 1, size, f);
fclose(f);
SigParser *p = malloc(sizeof(SigParser));
p->data = buf;
p->pos = 0;
p->len = size;
return p;
}
void sig_parser_free(SigParser *p) {
free(p->data);
free(p);
}
uint8_t read_byte(SigParser *p) {
return p->data[p->pos++];
}
size_t read_length(SigParser *p) {
uint8_t first = read_byte(p);
if (first < 192) {
return first;
} else if (first < 224) {
uint8_t second = read_byte(p);
return ((first - 192) * 256 + second) + 192;
} else {
read_byte(p); // 0xFF
uint32_t len = 0;
for (int i = 0; i < 4; i++) {
len |= (read_byte(p) << (i * 8));
}
return len;
}
}
void parse_sig(SigParser *p, char *props[20][100]) { // Simple key-value array
int idx = 0;
uint8_t ctb = read_byte(p);
sprintf(props[idx++], "packetTag: %u", ctb & 0x3F);
if ((ctb & 0x3F) != 2) { fprintf(stderr, "Not a signature packet\n"); return; }
size_t pkt_len = read_length(p);
sprintf(props[idx++], "packetLength: %zu", pkt_len);
uint8_t ver = read_byte(p);
sprintf(props[idx++], "version: %u", ver);
uint8_t sig_type = read_byte(p);
sprintf(props[idx++], "signatureType: %u", sig_type);
uint8_t pub_algo = read_byte(p);
sprintf(props[idx++], "pubKeyAlgo: %u", pub_algo);
uint8_t hash_algo = read_byte(p);
sprintf(props[idx++], "hashAlgo: %u", hash_algo);
if (ver == 3) {
uint32_t create_time = 0;
for (int i = 0; i < 4; i++) {
create_time |= (read_byte(p) << (i * 8));
}
sprintf(props[idx++], "creationTime: %u", create_time);
char key_id[17] = {0};
for (int i = 0; i < 8; i++) {
sprintf(key_id + i*2, "%02x", read_byte(p));
}
sprintf(props[idx++], "keyId: %s", key_id);
} else {
uint16_t hashed_len = (read_byte(p) | (read_byte(p) << 8));
sprintf(props[idx++], "hashedSubpacketLength: %u", hashed_len);
// Basic subpacket summary (hashed)
char sub_summary[256] = "hashedSubpackets: [";
int sub_idx = 0;
size_t sub_pos = p->pos;
while (sub_pos < p->pos + hashed_len) {
uint8_t sub_len_f = p->data[sub_pos++];
size_t sub_len = sub_len_f;
if (sub_len_f > 191) {
sub_len = ((sub_len_f - 192) * 256 + p->data[sub_pos++]) + 192;
}
uint8_t sub_type = p->data[sub_pos] & 0x7F;
if (sub_idx > 0) strcat(sub_summary, ", ");
char tmp[20]; sprintf(tmp, "{type:%u,len:%zu}", sub_type, sub_len);
strcat(sub_summary, tmp);
sub_pos += sub_len;
sub_idx++;
}
strcat(sub_summary, "]");
strcpy(props[idx++], sub_summary);
p->pos += hashed_len;
uint16_t unhashed_len = (read_byte(p) | (read_byte(p) << 8));
sprintf(props[idx++], "unhashedSubpacketLength: %u", unhashed_len);
// Similar for unhashed
strcpy(sub_summary, "unhashedSubpackets: [");
sub_idx = 0;
sub_pos = p->pos;
while (sub_pos < p->pos + unhashed_len) {
uint8_t sub_len_f = p->data[sub_pos++];
size_t sub_len = sub_len_f;
if (sub_len_f > 191) {
sub_len = ((sub_len_f - 192) * 256 + p->data[sub_pos++]) + 192;
}
uint8_t sub_type = p->data[sub_pos] & 0x7F;
if (sub_idx > 0) strcat(sub_summary, ", ");
char tmp[20]; sprintf(tmp, "{type:%u,len:%zu}", sub_type, sub_len);
strcat(sub_summary, tmp);
sub_pos += sub_len;
sub_idx++;
}
strcat(sub_summary, "]");
strcpy(props[idx++], sub_summary);
p->pos += unhashed_len;
}
char hash_left[5] = {0};
sprintf(hash_left, "%02x%02x", read_byte(p), read_byte(p));
sprintf(props[idx++], "hashLeft: %s", hash_left);
char hash_right[5] = {0};
sprintf(hash_right, "%02x%02x", read_byte(p), read_byte(p));
sprintf(props[idx++], "hashRight: %s", hash_right);
// Signature value summary
size_t sig_start = p->pos;
size_t sig_end = sig_start + (pkt_len - (p->pos - 3)); // Approx
char sig_hex[1024] = "signatureValue: ";
for (size_t i = sig_start; i < sig_end && i < p->len; i++) {
char tmp[4]; sprintf(tmp, "%02x ", p->data[i]);
strcat(sig_hex, tmp);
}
strcpy(props[idx++], sig_hex);
}
void print_properties(SigParser *p) {
char props[20][100];
parse_sig(p, props);
for (int i = 0; i < 20 && strlen(props[i]); i++) {
printf("%s\n", props[i]);
}
}
void write_file(SigParser *p, const char *output) {
FILE *f = fopen(output, "wb");
fwrite(p->data, 1, p->len, f);
fclose(f);
printf("Written to %s\n", output);
}
int main(int argc, char **argv) {
if (argc < 2) {
fprintf(stderr, "Usage: %s <file.sig>\n", argv[0]);
return 1;
}
SigParser *p = sig_parser_new(argv[1]);
if (!p) return 1;
print_properties(p);
write_file(p, "output.sig");
sig_parser_free(p);
return 0;
}
This C implementation uses manual byte manipulation for portability, summarizing subpackets and signature as hex strings for brevity. Full MPI parsing can be added if required.