Das std.pgp-Paket implementiert OpenPGP-Format-Parsing und ASCII-Armor nach RFC 4880. Es ist eine reine Parser- und Codec-Bibliothek: Kein Schlüssel-Generieren, kein Verschlüsseln, kein Entschlüsseln, kein Signieren, kein Verifizieren — nur das Lesen und Kodieren von PGP-Datenstrukturen.
Typische Einsatzfälle: .asc-Dateien dekodieren, öffentliche Schlüssel aus Keyrings lesen, Key-IDs und Fingerprints extrahieren, PGP-Paket-Ströme inspizieren.
→ Units-Übersicht · Lyx-Sprache
| Unit | Beschreibung |
|---|---|
std.pgp.core | Konstanten (Tags, Algorithmen, Signaturtypen, Armor-Typen), PgpPacketInfo-Struct-Offsets, PgpCrc24 |
std.pgp.armor | ASCII-Armor kodieren (PgpArmorEncode) und dekodieren (PgpArmorDecode), Typ erkennen (PgpArmorType) |
std.pgp.packet | Paket-Iteration über binäre PGP-Daten: PgpPacketFirst, PgpPacketNext |
std.pgp.key | Public-Key-Paket-Analyse: Version, Algorithmus, Zeitstempel, Fingerprint (SHA-1, v4), Key-ID |
| Konstante | Wert | Bedeutung |
|---|---|---|
PGP_TAG_PKESK | 1 | Public-Key Encrypted Session Key |
PGP_TAG_SIG | 2 | Signatur |
PGP_TAG_SKESK | 3 | Symmetric-Key Encrypted Session Key |
PGP_TAG_OPS | 4 | One-Pass Signature |
PGP_TAG_SECKEY | 5 | Geheimer Schlüssel |
PGP_TAG_PUBKEY | 6 | Öffentlicher Schlüssel |
PGP_TAG_SECSUBKEY | 7 | Geheimer Unterschlüssel |
PGP_TAG_COMPRESSED | 8 | Komprimierte Daten |
PGP_TAG_SYM_ENC | 9 | Symmetrisch verschlüsselte Daten |
PGP_TAG_LITERAL | 11 | Literal-Daten (eigentliche Nutzdaten) |
PGP_TAG_UID | 13 | User-ID |
PGP_TAG_PUBSUBKEY | 14 | Öffentlicher Unterschlüssel |
PGP_TAG_SEIPD | 18 | Symmetrisch verschlüsselte, integritätsgeschützte Daten |
| Konstante | Wert | Algorithmus |
|---|---|---|
PGP_ALG_RSA | 1 | RSA (Encrypt + Sign) |
PGP_ALG_RSA_E | 2 | RSA (nur Encrypt) |
PGP_ALG_RSA_S | 3 | RSA (nur Sign) |
PGP_ALG_ELGAMAL | 16 | ElGamal (nur Encrypt) |
PGP_ALG_DSA | 17 | DSA (nur Sign) |
PGP_ALG_ECDH | 18 | ECDH (nur Encrypt) |
PGP_ALG_ECDSA | 19 | ECDSA (nur Sign) |
PGP_ALG_EDDSA | 22 | EdDSA (nur Sign, z. B. Ed25519) |
| Konstante | Wert | Algorithmus |
|---|---|---|
PGP_HASH_MD5 | 1 | MD5 |
PGP_HASH_SHA1 | 2 | SHA-1 |
PGP_HASH_RIPEMD160 | 3 | RIPEMD-160 |
PGP_HASH_SHA256 | 8 | SHA-256 |
PGP_HASH_SHA384 | 9 | SHA-384 |
PGP_HASH_SHA512 | 10 | SHA-512 |
PGP_HASH_SHA224 | 11 | SHA-224 |
| Konstante | Wert | Algorithmus |
|---|---|---|
PGP_SYM_PLAINTEXT | 0 | Klartext (unverschlüsselt) |
PGP_SYM_IDEA | 1 | IDEA |
PGP_SYM_3DES | 2 | Triple-DES |
PGP_SYM_CAST5 | 3 | CAST5 |
PGP_SYM_BLOWFISH | 4 | Blowfish |
PGP_SYM_AES128 | 7 | AES-128 |
PGP_SYM_AES192 | 8 | AES-192 |
PGP_SYM_AES256 | 9 | AES-256 |
PGP_SYM_TWOFISH | 10 | Twofish |
| Konstante | Wert | Bedeutung |
|---|---|---|
PGP_SIG_BINARY | 0x00 | Binärdokument |
PGP_SIG_TEXT | 0x01 | Textdokument (CRLF-normalisiert) |
PGP_SIG_CERT_GENERIC | 0x10 | Generische Key-Zertifizierung |
PGP_SIG_CERT_POSITIVE | 0x13 | Positive Key-Zertifizierung |
PGP_SIG_SUBKEY_BIND | 0x18 | Subkey-Bindung |
PGP_SIG_REVOKE_KEY | 0x20 | Schlüssel-Widerruf |
PGP_SIG_REVOKE_SUBKEY | 0x28 | Unterschlüssel-Widerruf |
PGP_SIG_TIMESTAMP | 0x40 | Zeitstempel |
| Konstante | Wert | BEGIN-Header |
|---|---|---|
PGP_ARMOR_UNKNOWN | 0 | Unbekannt / kein Armor |
PGP_ARMOR_MESSAGE | 1 | BEGIN PGP MESSAGE |
PGP_ARMOR_PUBLIC_KEY | 2 | BEGIN PGP PUBLIC KEY BLOCK |
PGP_ARMOR_PRIVATE_KEY | 3 | BEGIN PGP PRIVATE KEY BLOCK |
PGP_ARMOR_SIGNATURE | 4 | BEGIN PGP SIGNATURE |
RFC 4880 §6.1 schreibt CRC-24 als Prüfsumme im ASCII-Armor vor. Init: 0xB704CE, Polynom: 0x1864CFB.
import std.pgp.core;
import std.alloc;
fn main(): int64 {
var data: pchar := "Hallo OpenPGP"c;
var crc: int64 := PgpCrc24(data as int64, 13);
// crc = 24-Bit Wert (Bits 23..0)
return 0;
}
PgpArmorEncode ruft PgpCrc24 intern auf — bei normalem Armor-Workflow muss die Funktion nicht direkt aufgerufen werden.
ASCII-Armor kodiert beliebige Binärdaten als druckbaren Text im —–BEGIN PGP …—–-Format. PGP-Schlüssel und Signaturen werden fast immer in diesem Format ausgetauscht.
import std.pgp.armor;
import std.pgp.core;
import std.alloc;
import std.io;
fn EncodePublicKey(rawKey: int64, keyLen: int64): void {
// Worst-Case: ca. 4/3 × Rohdaten + Header/Footer (~100 Bytes) + CRC
var outMax: int64 := (keyLen * 4 / 3) + 200;
var out: int64 := alloc(outMax);
var encLen: int64 := PgpArmorEncode(rawKey, keyLen, PGP_ARMOR_PUBLIC_KEY, out, outMax);
// out enthält jetzt:
// -----BEGIN PGP PUBLIC KEY BLOCK-----
//
// <base64, 76 Zeichen/Zeile>
// =<CRC4>
// -----END PGP PUBLIC KEY BLOCK-----
PrintLn(out as pchar);
free(out, outMax);
}
PgpArmorEncode gibt die tatsächliche Ausgabelänge (ohne NUL-Byte) zurück. Die 76-Zeichen-Zeilen entstehen, weil immer 57 Eingangsbytes → 76 Base64-Zeichen kodiert werden.
import std.pgp.armor;
import std.pgp.core;
import std.alloc;
// Gibt rohe Binärdaten zurück (muss mit free freigegeben werden),
// oder 0 bei CRC-Fehler / ungültigem Armor.
fn DecodeArmor(armText: int64, armLen: int64, outLen: int64): int64 {
var outMax: int64 := armLen; // dekodiert immer kleiner als Armor-Text
var out: int64 := alloc(outMax);
var n: int64 := PgpArmorDecode(armText, armLen, out, outMax);
if (n < 0) {
free(out, outMax);
return 0; // CRC-Fehler oder kein gültiger Armor
}
poke64(outLen, n);
return out; // Aufrufer: free(out, outMax) nach Gebrauch
}
PgpArmorDecode überspringt Header-Attributzeilen (z. B. Version: GnuPG v2), liest alle Base64-Zeilen und verifiziert die CRC-24-Prüfsumme. Bei CRC-Fehler oder fehlendem BEGIN PGP-Header wird -1 zurückgegeben.
import std.pgp.armor;
import std.pgp.core;
import std.io;
fn PrintArmorType(arm: int64, armLen: int64): void {
var tp: int64 := PgpArmorType(arm, armLen);
if (tp == PGP_ARMOR_PUBLIC_KEY) { PrintLn("Öffentlicher Schlüssel"); }
if (tp == PGP_ARMOR_PRIVATE_KEY) { PrintLn("Privater Schlüssel"); }
if (tp == PGP_ARMOR_MESSAGE) { PrintLn("Nachricht"); }
if (tp == PGP_ARMOR_SIGNATURE) { PrintLn("Signatur"); }
if (tp == PGP_ARMOR_UNKNOWN) { PrintLn("Unbekannt"); }
}
PgpArmorType sucht nur nach dem BEGIN PGP …-Header, ohne die Daten zu dekodieren.
PGP-Binärdaten bestehen aus einer Folge von Paketen. PgpPacketFirst und PgpPacketNext iterieren über sie, ohne Kopien zu erstellen — PGP_PKT_OFF_BODY ist ein direkter Zeiger in den Quell-Buffer.
Der Aufrufer alloziert einen 32-Byte-Puffer (PGP_PKT_SIZE):
| Konstante | Offset | Inhalt |
|---|---|---|
PGP_PKT_OFF_TAG | 0 | Paket-Typ (PGP_TAG_*) |
PGP_PKT_OFF_BODY | 8 | Absoluter Zeiger auf Paket-Body-Bytes im Quell-Buffer |
PGP_PKT_OFF_BLEN | 16 | Body-Länge in Bytes |
PGP_PKT_OFF_NEXT | 24 | Byte-Offset im Quell-Buffer für das nächste Paket |
PGP_PKT_SIZE | 32 | Gesamtgröße des Structs |
import std.pgp.core;
import std.pgp.packet;
import std.alloc;
import std.io;
fn ScanPackets(buf: int64, len: int64): void {
var pkt: int64 := alloc(PGP_PKT_SIZE);
if (PgpPacketFirst(buf, len, pkt) == 0) {
PrintLn("Keine Pakete gefunden");
free(pkt, PGP_PKT_SIZE);
return;
}
var more: int64 := 1;
while (more != 0) {
var tag: int64 := peek64(pkt + PGP_PKT_OFF_TAG);
var body: int64 := peek64(pkt + PGP_PKT_OFF_BODY);
var blen: int64 := peek64(pkt + PGP_PKT_OFF_BLEN);
if (tag == PGP_TAG_PUBKEY) { PrintLn("Öffentlicher Schlüssel (" + IntToStr(blen) + " Bytes)"); }
if (tag == PGP_TAG_UID) { PrintLn("User-ID"); }
if (tag == PGP_TAG_SIG) { PrintLn("Signatur"); }
if (tag == PGP_TAG_PUBSUBKEY) { PrintLn("Öffentlicher Unterschlüssel"); }
more := PgpPacketNext(buf, len, pkt);
}
free(pkt, PGP_PKT_SIZE);
}
Unterstützte Paket-Formate: Old-format (ll=0,1,2,3 gemäß RFC 4880 §4.2.1) und New-format (1-Byte-, 2-Byte-, 5-Byte-Längen gemäß §4.2.2). Partial-body-Header werden als vollständige Pakete behandelt.
Alle Funktionen arbeiten auf dem Paket-Body — d.h. dem Buffer-Bereich, der über PGP_PKT_OFF_BODY / PGP_PKT_OFF_BLEN erreichbar ist, ohne den Paket-Header (Tag-Byte + Längenfeld).
import std.pgp.core;
import std.pgp.packet;
import std.pgp.key;
import std.alloc;
import std.io;
fn PrintKeyInfo(buf: int64, len: int64): void {
var pkt: int64 := alloc(PGP_PKT_SIZE);
if (PgpPacketFirst(buf, len, pkt) == 0) {
free(pkt, PGP_PKT_SIZE); return;
}
var more: int64 := 1;
while (more != 0) {
var tag: int64 := peek64(pkt + PGP_PKT_OFF_TAG);
var body: int64 := peek64(pkt + PGP_PKT_OFF_BODY);
var blen: int64 := peek64(pkt + PGP_PKT_OFF_BLEN);
if (tag == PGP_TAG_PUBKEY || tag == PGP_TAG_PUBSUBKEY) {
var ver: int64 := PgpKeyVersion(body, blen);
var algo: int64 := PgpKeyAlgo(body, blen);
var ts: int64 := PgpKeyTimestamp(body, blen);
PrintLn("Version: " + IntToStr(ver));
PrintLn("Zeitstempel: " + IntToStr(ts));
if (algo == PGP_ALG_RSA) { PrintLn("Algorithmus: RSA"); }
if (algo == PGP_ALG_DSA) { PrintLn("Algorithmus: DSA"); }
if (algo == PGP_ALG_ECDSA) { PrintLn("Algorithmus: ECDSA"); }
if (algo == PGP_ALG_EDDSA) { PrintLn("Algorithmus: EdDSA"); }
if (algo == PGP_ALG_ECDH) { PrintLn("Algorithmus: ECDH"); }
}
more := PgpPacketNext(buf, len, pkt);
}
free(pkt, PGP_PKT_SIZE);
}
PgpKeyFingerprint und PgpKeyId funktionieren nur bei v4-Schlüsseln. Bei v3-Schlüsseln geben sie 0 zurück.
import std.pgp.core;
import std.pgp.packet;
import std.pgp.key;
import std.alloc;
import std.io;
fn PrintFingerprint(buf: int64, len: int64): void {
var pkt: int64 := alloc(PGP_PKT_SIZE);
var fp: int64 := alloc(20);
var kid: int64 := alloc(8);
if (PgpPacketFirst(buf, len, pkt) != 0) {
var tag: int64 := peek64(pkt + PGP_PKT_OFF_TAG);
var body: int64 := peek64(pkt + PGP_PKT_OFF_BODY);
var blen: int64 := peek64(pkt + PGP_PKT_OFF_BLEN);
if (tag == PGP_TAG_PUBKEY && PgpKeyFingerprint(body, blen, fp) != 0) {
// Fingerprint hex ausgeben (20 Bytes)
var i: int64 := 0;
while (i < 20) {
var b: int64 := peek8(fp + i);
Print(IntToStr((b >> 4) & 15)); // Oberes Nibble
Print(IntToStr(b & 15)); // Unteres Nibble
i := i + 1;
}
PrintLn("");
}
if (tag == PGP_TAG_PUBKEY && PgpKeyId(body, blen, kid) != 0) {
// Key-ID = letzte 8 Bytes des Fingerprints
Print("Key-ID: ");
var i: int64 := 0;
while (i < 8) {
Print(IntToStr(peek8(kid + i)));
i := i + 1;
}
PrintLn("");
}
}
free(pkt, PGP_PKT_SIZE);
free(fp, 20);
free(kid, 8);
}
Der Fingerprint wird nach RFC 4880 §12.2 berechnet: SHA-1 über 0x99 || uint16_BE(blen) || body. Die Key-ID sind die letzten 8 Bytes des Fingerprints (Bytes 12–19).
import std.pgp.core;
import std.pgp.armor;
import std.pgp.packet;
import std.pgp.key;
import std.alloc;
import std.fs;
import std.io;
fn ReadAscFile(path: int64): void {
// Datei laden
var fsize: int64 := FileSize(path);
if (fsize <= 0) { return; }
var armBuf: int64 := alloc(fsize);
ReadFile(path, armBuf, fsize);
// Armor-Typ prüfen
var tp: int64 := PgpArmorType(armBuf, fsize);
if (tp != PGP_ARMOR_PUBLIC_KEY) {
PrintLn("Kein öffentlicher Schlüssel");
free(armBuf, fsize);
return;
}
// Armor dekodieren
var rawBuf: int64 := alloc(fsize);
var rawLen: int64 := PgpArmorDecode(armBuf, fsize, rawBuf, fsize);
free(armBuf, fsize);
if (rawLen < 0) {
PrintLn("CRC-Fehler oder ungültiger Armor");
free(rawBuf, fsize);
return;
}
// Pakete durchlaufen
var pkt: int64 := alloc(PGP_PKT_SIZE);
var fp: int64 := alloc(20);
if (PgpPacketFirst(rawBuf, rawLen, pkt) != 0) {
var more: int64 := 1;
while (more != 0) {
var tag: int64 := peek64(pkt + PGP_PKT_OFF_TAG);
var body: int64 := peek64(pkt + PGP_PKT_OFF_BODY);
var blen: int64 := peek64(pkt + PGP_PKT_OFF_BLEN);
if (tag == PGP_TAG_PUBKEY) {
PrintLn("Hauptschlüssel v" + IntToStr(PgpKeyVersion(body, blen)));
PgpKeyFingerprint(body, blen, fp);
// fp enthält 20-Byte SHA-1 Fingerprint
}
if (tag == PGP_TAG_UID) {
// User-ID-Body ist ein UTF-8-String
poke8(body + blen, 0); // NUL-terminieren (temporär)
PrintLn("UID: " + (body as pchar));
}
if (tag == PGP_TAG_PUBSUBKEY) {
PrintLn("Unterschlüssel (" + IntToStr(blen) + " Bytes)");
}
more := PgpPacketNext(rawBuf, rawLen, pkt);
}
}
free(pkt, PGP_PKT_SIZE);
free(fp, 20);
free(rawBuf, fsize);
}
Hinweis zum User-ID-Body: Dasbody-Feld zeigt direkt in den Quell-Buffer. Das temporäre NUL-Terminieren (poke8(body + blen, 0)) setzt das erste Byte des nächsten Pakets auf 0. Nur sicher, wenn der Buffer danach nicht mehr vollständig iteriert werden muss — sonst eine Kopie anlegen.
| Was | Einschränkung |
|---|---|
| Schlüssel-Generierung | Nicht vorhanden — kein RSA/DSA/ECDSA/EdDSA KeyGen |
| Verschlüsseln / Entschlüsseln | Nicht vorhanden |
| Signieren / Verifizieren | Nicht vorhanden |
| v3-Fingerprint | Nicht unterstützt (v3-Schlüssel liefern nur Version/Algo/Timestamp) |
| Partial-body-Pakete | Werden als vollständig behandelt; verkettete Partial-Pakete werden nicht zusammengeführt |
| Compressed Data (Tag 8) | Wird als Paket erkannt, aber nicht dekomprimiert |
| Key-ID-Format | 32-Bit Short Key-ID nicht direkt; aus den letzten 4 Bytes der 8-Byte Key-ID ableiten |
| Datei | Inhalt |
|---|---|
std/pgp/core.lyx | Alle PGP_TAG_*, PGP_ALG_*, PGP_HASH_*, PGP_SYM_*, PGP_SIG_*, PGP_ARMOR_*-Konstanten; PgpPacketInfo-Offsets; PgpCrc24 (RFC 4880 §6.1) |
std/pgp/armor.lyx | PgpArmorEncode, PgpArmorDecode (Base64 + CRC-24-Verifikation), PgpArmorType |
std/pgp/packet.lyx | PgpPacketFirst, PgpPacketNext; Old- und New-Format-Paket-Parser |
std/pgp/key.lyx | PgpKeyVersion, PgpKeyAlgo, PgpKeyTimestamp, PgpKeyFingerprint (v4 SHA-1), PgpKeyId |
Letzte Aktualisierung: 2026-06-15