Inhaltsverzeichnis

std.pgp — OpenPGP (RFC 4880)

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


Units

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

Konstanten (std.pgp.core)

Paket-Tags (RFC 4880 §4.3)

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

Public-Key-Algorithmen (RFC 4880 §9.1)

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)

Hash-Algorithmen (RFC 4880 §9.4)

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

Symmetrische Algorithmen (RFC 4880 §9.2)

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

Signatur-Typen (RFC 4880 §5.2.1)

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

Armor-Typen

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

CRC-24 (std.pgp.core)

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 (std.pgp.armor)

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.

Kodieren

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.

Dekodieren

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.

Typ erkennen

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.


Paket-Iteration (std.pgp.packet)

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.

PgpPacketInfo-Struct

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

Alle Pakete durchlaufen

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.


Schlüssel-Analyse (std.pgp.key)

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).

Version, Algorithmus, Zeitstempel

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);
}

Fingerprint und Key-ID

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).


Vollständiges Beispiel: .asc-Datei auslesen

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: Das body-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.

Einschränkungen

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

Quelldateien

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