====== 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