Hier ist eine minimale, lauffähige Demo: ein kleines HTML-Formular mit Canvas-Unterschrift + ein PHP-Handler, der alle Daten in ein ZIP packt, mit PGP verschlüsselt und per Mail verschickt.
<!doctype html><html lang="de"><head>
<meta charset="utf-8" />
<title>Lehrmittelzuschuss – Antrag (Demo)</title>
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>
body { font-family: system-ui, sans-serif; margin: 2rem; max-width: 800px; }
label { display:block; margin-top: .75rem; font-weight: 600; }
input, textarea { width: 100%; padding: .5rem; }
canvas { border: 1px solid #ccc; border-radius: 8px; width: 100%; height: 200px; touch-action: none; }
.row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
button { padding: .6rem 1rem; margin-right: .5rem; }
</style></head><body>
<h1>Lehrmittelzuschuss – Antrag (Demo)</h1>
<form action="/submit.php" method="post" enctype="multipart/form-data">
<div class="row">
<div>
<label>Ihr Name</label>
<input name="name" required>
</div>
<div>
<label>E-Mail</label>
<input type="email" name="email" required>
</div>
</div>
<div class="row">
<div>
<label>Kind (Name)</label>
<input name="child_name" required>
</div>
<div>
<label>Geburtsdatum Kind</label>
<input type="date" name="child_dob" required>
</div>
</div>
<label>Nachweise (mehrere möglich)</label>
<input type="file" name="attachments[]" multiple accept=".pdf,.png,.jpg,.jpeg,.heic,.webp">
<label>Unterschrift</label>
<canvas id="sig" width="800" height="200"></canvas>
<div style="margin:.5rem 0 1rem">
<button type="button" id="clear">Unterschrift löschen</button>
</div>
<input type="hidden" name="signature_png" id="signature_png" required>
<button type="submit">Sicher absenden</button>
</form>
<script>
// Simple Signature Pad (ohne externe Lib)
const canvas = document.getElementById('sig');
const ctx = canvas.getContext('2d');
let drawing = false, last = null;
function pos(e){
const r = canvas.getBoundingClientRect();
const t = (touch = e.touches && e.touches[0]) ? touch : e;
return { x: (t.clientX - r.left) * (canvas.width / r.width),
y: (t.clientY - r.top) * (canvas.height / r.height) };
}
function start(e){ drawing = true; last = pos(e); e.preventDefault(); }
function end(){ drawing = false; last = null; }
function move(e){
if(!drawing) return;
const p = pos(e);
ctx.lineWidth = 2.0; ctx.lineCap = 'round';
ctx.beginPath(); ctx.moveTo(last.x, last.y); ctx.lineTo(p.x, p.y); ctx.stroke();
last = p; e.preventDefault();
}
canvas.addEventListener('mousedown', start);
canvas.addEventListener('mousemove', move);
window.addEventListener('mouseup', end);
canvas.addEventListener('touchstart', start, {passive:false});
canvas.addEventListener('touchmove', move, {passive:false});
canvas.addEventListener('touchend', end);
document.getElementById('clear').onclick = () => {
ctx.clearRect(0,0,canvas.width,canvas.height);
document.getElementById('signature_png').value = "";
};
// Beim Absenden Canvas als PNG übernehmen
document.querySelector('form').addEventListener('submit', (e) => {
const data = canvas.toDataURL('image/png');
// Primitive Prüfung: ist die Signatur leer (alles transparent)?
// Hier: wenn die DataURL sehr kurz ist, brechen wir ab.
if (data.length < 2000) { // quick&dirty
e.preventDefault();
alert('Bitte unterschreiben.');
return;
}
document.getElementById('signature_png').value = data;
});
</script></body></html>
Benötigt: php-gnupg oder GnuPG-CLI (gpg) als Fallback.
<?php
// --- Minimal-Konfiguration ---
$RECIPIENT = 'it@example.gov'; // Empfänger (Mail + GPG-UID/Fingerprint)
$MAIL_TO = 'it@example.gov'; // Zieladresse
$MAIL_SUBJ = 'Antrag Lehrmittelzuschuss (PGP-verschlüsselt)';
$FROM = 'no-reply@deine-domain.tld';
// --- Sicherheitsgurt: nur HTTPS in Produktion ---
if (empty($_SERVER['HTTPS']) || $_SERVER['HTTPS'] === 'off') {
// In Produktion: exit('HTTPS required');
}
// --- Pflichtfelder prüfen ---
foreach (['name','email','child_name','child_dob','signature_png'] as $f) {
if (empty($_POST[$f])) { http_response_code(400); exit("Fehlendes Feld: $f"); }
}
// --- ZIP in Temp bauen ---
$tmpZip = tempnam(sys_get_temp_dir(), 'antrag_') . '.zip';
$zip = new ZipArchive();
if ($zip->open($tmpZip, ZipArchive::CREATE)!==TRUE) { http_response_code(500); exit('ZIP Fehler'); }
// 1) Formdaten als JSON
$data = [
'name' => $_POST['name'],
'email' => $_POST['email'],
'child_name' => $_POST['child_name'],
'child_dob' => $_POST['child_dob'],
'submitted' => gmdate('c')
];
$zip->addFromString('antrag.json', json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_UNICODE));
// 2) Unterschrift (Base64 → PNG)
if (preg_match('#^data:image/png;base64,#', $_POST['signature_png'])) {
$png = base64_decode(preg_replace('#^data:image/\w+;base64,#', '', $_POST['signature_png']));
$zip->addFromString('unterschrift.png', $png);
} else {
$zip->close(); @unlink($tmpZip); http_response_code(400); exit('Ungültige Signatur');
}
// 3) Anhänge (optional)
if (!empty($_FILES['attachments']) && is_array($_FILES['attachments']['name'])) {
for ($i=0; $i<count($_FILES['attachments']['name']); $i++) {
if ($_FILES['attachments']['error'][$i] === UPLOAD_ERR_OK) {
$name = basename($_FILES['attachments']['name'][$i]);
$safe = preg_replace('/[^a-zA-Z0-9._-]/','_', $name);
$zip->addFile($_FILES['attachments']['tmp_name'][$i], "anlagen/$safe");
}
}
}
$zip->close();
$zipData = file_get_contents($tmpZip);
@unlink($tmpZip);
if ($zipData === false) { http_response_code(500); exit('ZIP lesen fehlgeschlagen'); }
// --- PGP verschlüsseln (php-gnupg bevorzugt, sonst CLI) ---
$cipher = null;
if (extension_loaded('gnupg')) {
$gpg = new gnupg();
$gpg->seterrormode(GNUPG_ERROR_EXCEPTION);
// Wichtig: Öffentlichen Schlüssel des Empfängers muss auf dem Server-Schlüsselbund vorhanden sein
$gpg->addencryptkey($RECIPIENT);
$cipher = $gpg->encrypt($zipData);
} else {
// Fallback via gpg CLI (öffentlicher Schlüssel muss importiert sein)
$in = tempnam(sys_get_temp_dir(), 'in_');
$out = tempnam(sys_get_temp_dir(), 'out_') . '.gpg';
file_put_contents($in, $zipData);
$cmd = sprintf('gpg --batch --yes --trust-model always -r %s -o %s -e %s 2>&1',
escapeshellarg($RECIPIENT), escapeshellarg($out), escapeshellarg($in));
$ret = shell_exec($cmd);
@unlink($in);
if (!file_exists($out)) { @unlink($out); http_response_code(500); exit("GPG Fehler:\n".$ret); }
$cipher = file_get_contents($out);
@unlink($out);
}
if (!$cipher) { http_response_code(500); exit('Verschlüsselung fehlgeschlagen'); }
// --- Mail mit .gpg Anhang versenden (Minimal, für produktiv PHPMailer o.ä.) ---
$boundary = 'b'.bin2hex(random_bytes(8));
$headers = [];
$headers[] = "From: $FROM";
$headers[] = "MIME-Version: 1.0";
$headers[] = "Content-Type: multipart/mixed; boundary=\"$boundary\"";
$body = "--$boundary\r\n";
$body .= "Content-Type: text/plain; charset=UTF-8\r\n\r\n";
$body .= "Anbei der verschlüsselte Antrag als .gpg. Bitte mit dem IT-Schlüssel entschlüsseln.\r\n";
$body .= "\r\n";
$body .= "--$boundary\r\n";
$body .= "Content-Type: application/octet-stream; name=\"antrag.zip.gpg\"\r\n";
$body .= "Content-Transfer-Encoding: base64\r\n";
$body .= "Content-Disposition: attachment; filename=\"antrag.zip.gpg\"\r\n\r\n";
$body .= chunk_split(base64_encode($cipher));
$body .= "--$boundary--\r\n";
if (!mail($MAIL_TO, $MAIL_SUBJ, $body, implode("\r\n", $headers))) {
http_response_code(500); exit('Mailversand fehlgeschlagen');
}
// --- Fertig ---
echo "Danke! Ihr Antrag wurde **sicher** übertragen.";
GnuPG + PHP-Modul installieren (Beispiele Ubuntu/Debian):
sudo apt-get install gnupg php-gnupg
sudo systemctl reload php*-fpm || sudo systemctl reload apache2
Öffentlichen Schlüssel der IT importieren (Server):
gpg --import /pfad/zum/publickey.asc
gpg --list-keys
# Fingerprint/UID merken und in $RECIPIENT eintragen (submit.php)
Mailversand sicherstellen:
Für Demo reicht mail() (Postfix/Exim vorhanden). Besser: PHPMailer mit SMTP.
index.html
unter /var/www/html/index.html
submit.php
unter /var/www/html/submit.php