====== 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. → [[lyx_-_programmiersprache:units|Units-Übersicht]] · [[lyx_-_programmiersprache:start|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----- // // // = // -----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