Encryption 1™ - Online : Quellcode


<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Encryption 1</title>
  <meta name="description" content="Kostenloses browserbasiertes Verschlüsselungstool. Text und Dateien lokal mit AES-GCM verschlüsseln und entschlüsseln – ohne Konto, ohne Installation, ohne Serverspeicherung. Funktioniert offline." />
  <link rel="canonical" href="https://www.encryption-one.com/de/ec1.html" />
  <link rel="alternate" hreflang="en" href="https://www.encryption-one.com/ec1.html" />
  <link rel="alternate" hreflang="de" href="https://www.encryption-one.com/de/ec1.html" />
  <link rel="alternate" hreflang="es" href="https://www.encryption-one.com/es/ec1.html" />
  <link rel="alternate" hreflang="fr" href="https://www.encryption-one.com/fr/ec1.html" />
  <link rel="alternate" hreflang="ja" href="https://www.encryption-one.com/ja/ec1.html" />
  <link rel="alternate" hreflang="x-default" href="https://www.encryption-one.com/ec1.html" />
  <meta property="og:type" content="website" />
  <meta property="og:site_name" content="Encryption 1" />
  <meta property="og:title" content="Encryption 1" />
  <meta property="og:description" content="Kostenloses browserbasiertes Verschlüsselungstool. Text und Dateien lokal mit AES-GCM verschlüsseln und entschlüsseln – ohne Konto, ohne Installation, ohne Serverspeicherung. Funktioniert offline." />
  <meta property="og:url" content="https://www.encryption-one.com/de/ec1.html" />
  <meta property="og:locale" content="de_DE" />
  <meta property="og:locale:alternate" content="en_US" />
  <meta property="og:locale:alternate" content="es_ES" />
  <meta property="og:locale:alternate" content="fr_FR" />
  <meta property="og:locale:alternate" content="ja_JP" />
  <!-- og:image: add once a preview image is ready, e.g.
       <meta property="og:image" content="https://www.encryption-one.com/og-image.png" /> -->

<style>
  :root {
    --bg: #f5f7fb;
    --card: #ffffff;
    --text: #1f2937;
    --muted: #6b7280;
    --border: #dbe3ee;
    --accent: #0d6efd;
    --accent-2: #0b5ed7;
    --danger: #b42318;
    --success: #157347;
    --warning-bg: #ffe6e6;
    --warning-border: #ff8080;
    --shadow: 0 10px 30px rgba(0,0,0,.08);
  }

  * { box-sizing: border-box; }

  body {
    margin: 0;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    background: linear-gradient(180deg, #eef4ff 0%, var(--bg) 100%);
    color: var(--text);
    min-height: 100vh;
  }

  .nolines { text-decoration: none; font-size: small; }

  .container { max-width: 980px; margin: 0 auto; padding: 24px; }

  .hero { text-align: center; margin-bottom: 20px; }
  .hero h1 { margin: 0 0 8px; font-size: clamp(1.8rem, 3vw, 2.4rem); }
  .hero p { margin: 0; color: var(--muted); line-height: 1.55; }

  .badge {
    display: inline-block;
    background: #e9f2ff;
    color: #0a58ca;
    border-radius: 999px;
    padding: 6px 12px;
    font-size: .85rem;
    font-weight: 700;
    margin-bottom: 10px;
  }

  .warning {
    margin: 20px 0;
    padding: 5px 5px;
    border: 1px solid var(--warning-border);
    background: var(--warning-bg);
    border-radius: 14px;
    color: #5f4b00;
    line-height: 1.5;
    font-size: small;
    text-align: center;
  }

  .card {
    background: var(--card);
    border: 1px solid var(--border);
    border-radius: 20px;
    box-shadow: var(--shadow);
    overflow: hidden;
  }

  .tabs { display: flex; border-bottom: 1px solid var(--border); background: #fbfcff; flex-wrap: wrap; }
  .tab-btn {
    appearance: none;
    border: 0;
    background: transparent;
    padding: 16px 20px;
    cursor: pointer;
    font: inherit;
    font-size: 1rem;
    color: var(--muted);
    border-bottom: 3px solid transparent;
    transition: .2s ease;
  }
  .tab-btn:hover,
  .tab-btn.active {
    color: var(--accent);
    border-bottom-color: var(--accent);
    background: rgba(13, 110, 253, .04);
  }

  .panel { display: none; padding: 24px; }
  .panel.active { display: block; }

  .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
  @media (max-width: 820px) {
    .grid { grid-template-columns: 1fr; }
  }

  label { display: block; font-weight: 600; margin-bottom: 8px; }

  textarea,
  input[type="password"],
  input[type="text"],
  input[type="file"] {
    width: 100%;
    padding: 12px 14px;
    border: 1px solid var(--border);
    border-radius: 14px;
    font: inherit;
    background: #fff;
    color: var(--text);
  }
  textarea { resize: vertical; line-height: 1.5; }

  .field { margin-bottom: 16px; }
  .help { margin-top: 6px; font-size: .92rem; color: var(--muted); line-height: 1.45; }
  .actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }

  button {
    appearance: none;
    border: 0;
    cursor: pointer;
    padding: 12px 16px;
    border-radius: 14px;
    font: inherit;
    font-weight: 600;
    transition: transform .05s ease, background .2s ease, opacity .2s ease;
  }
  button:active { transform: translateY(1px); }
  button:disabled { opacity: .55; cursor: not-allowed; }

  .btn-primary { background: var(--accent); color: #fff; }
  .btn-primary:hover:not(:disabled) { background: var(--accent-2); }
  .btn-secondary { background: #eef2f7; color: var(--text); }
  .btn-secondary:hover:not(:disabled) { background: #e3e9f2; }
  .btn-secondary.small { padding: 8px 12px; font-size: .85rem; white-space: nowrap; flex: none; }
  .btn-danger { background: #fbeaea; color: var(--danger); }
  .btn-danger:hover:not(:disabled) { background: #f7dede; }

  .status { margin-top: 16px; min-height: 24px;font-size:small;}
  .status.error { color: var(--danger); }
  .status.success { color: var(--success); }

  .meta {
    margin-top: 16px;
    padding: 14px 16px;
    border-radius: 14px;
    background: #f8fafc;
    border: 1px solid var(--border);
    color: var(--muted);
    line-height: 1.5;
    font-size: .95rem;
  }

  .mono {
    font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
    word-break: break-word;
  }

  .footer { margin-top: 18px; color: var(--muted); font-size: .9rem; line-height: 1.5; }

  .strength-wrap { margin-top: 10px; }
  .strength-bar {
    width: 100%;
    height: 10px;
    border-radius: 999px;
    background: #e9eef5;
    border: 1px solid var(--border);
    overflow: hidden;
  }
  .strength-fill {
    height: 100%;
    width: 0;
    border-radius: 999px;
    background: #d0d7e2;
    transition: width .2s ease, background .2s ease;
  }
  .strength-text { margin-top: 8px; font-size: .92rem; color: var(--muted); line-height: 1.4; }

  .dropzone {
    border: 2px dashed #b9c9dc;
    border-radius: 18px;
    padding: 28px 18px;
    text-align: center;
    background: #f8fbff;
    cursor: pointer;
    transition: .15s;
  }
  .dropzone:hover,
  .dropzone.dragover { border-color: var(--accent); background: #eef6ff; }
  .dropzone strong { display: block; font-size: 1.05rem; margin-bottom: 5px; }

  .file-info {
    margin-top: 12px;
    padding: 12px 14px;
    background: #f8fafc;
    border: 1px solid var(--border);
    border-radius: 12px;
    font-size: .92rem;
  }
  .limit { font-weight: 700; color: #0a58ca; }

  .mode-note {
    padding: 12px 14px;
    border-radius: 12px;
    background: #eef6ff;
    color: #0a58ca;
    margin-bottom: 18px;
    font-size: .92rem;
  }

  .hidden { display: none; }

  .password-row { display: flex; gap: 8px; align-items: stretch; }
  .password-row input { flex: 1; min-width: 0; }
</style>
</head>
<body>
<div class="container">
    <div class="hero">
      <h1>🔐 Encryption 1</h1>
      <p><strong>Encryption 1</strong>  •  
	  <a class="nolines" href="tagebuch.html">EC-1 Tagebuch</a>  •  
	  <a class="nolines" href="faq.html">Häufig gestellte Fragen</a>  •  
	  <a class="nolines" href="sicherheitsleitfaden.html">Sicherheitsleitfaden</a></p>
    </div>
    <div class="warning">
      <strong>Wichtig:</strong> Wenn Sie Ihr Passwort vergessen, können Ihre Dateien und Texte nicht wiederhergestellt werden.
    </div>
  <div class="card">
    <div class="tabs">
      <button class="tab-btn active" data-tab="encryptText">Text verschlüsseln</button>
      <button class="tab-btn" data-tab="encryptFile">Datei verschlüsseln</button>
      <button class="tab-btn" data-tab="decrypt">Entschlüsseln</button>
    </div>

    <div id="encryptText" class="panel active">
      <!-- <div class="mode-note">The original Encryption 1 text workflow is preserved here.</div> -->
      <div class="grid">
        <div class="field"><label for="plainText">Zu verschlüsselnder Text</label><textarea id="plainText" rows="13" placeholder="Text hier eingeben oder einfügen..."></textarea></div>
        <div>
          <div class="field"><label for="textPassword">Passwort / Passphrase</label><div class="password-row"><input id="textPassword" type="password" placeholder="Geben Sie ein starkes Passwort ein" autocomplete="new-password"><button id="textToggleBtn" class="btn-secondary small" type="button" aria-pressed="false" aria-label="Passwort anzeigen">Anzeigen</button><button id="textGenerateBtn" class="btn-secondary small" type="button">Generieren</button></div><div class="strength-wrap"><div class="strength-bar"><div id="textStrengthBar" class="strength-fill"></div></div><div id="textStrengthText" class="strength-text">Passwortstärke: noch nicht eingegeben</div></div><div id="textStatus" class="status" aria-live="polite"></div></div>
          <div class="field"><label for="textPassword2">Passwort bestätigen</label><input id="textPassword2" type="password" placeholder="Passwort erneut eingeben" autocomplete="new-password"></div>
          <div class="field"><label for="textFilename">Dateiname für den Download</label><input id="textFilename" type="text" value="private-note.pvault"></div>
          <div class="actions"><button id="encryptTextBtn" class="btn-primary">Verschlüsseln und herunterladen</button><button id="clearTextBtn" class="btn-danger">Löschen</button></div>
          
		  <div id="textMeta" class="meta hidden"></div>
        </div>
      </div>
    </div>

    <div id="encryptFile" class="panel">
      <!-- <div class="mode-note"><strong>File limit: 10 MB.</strong> Images, PDFs, Word documents, spreadsheets, ZIP files, and other files can be encrypted as raw bytes.</div> -->
      <div class="grid">
        <div>
          <label>Datei auswählen</label>
          <div id="dropzone" class="dropzone" tabindex="0" role="button"><strong>Datei hierher ziehen</strong><span>oder klicken, um eine auszuwählen</span><div class="help">Maximale Dateigröße: <span class="limit">10 MB</span></div></div>
          <input id="fileInput" type="file" class="hidden">
          <div id="fileInfo" class="file-info hidden"></div>
        </div>
        <div>
          <div class="field"><label for="filePassword">Passwort / Passphrase</label><div class="password-row"><input id="filePassword" type="password" placeholder="Geben Sie ein starkes Passwort ein" autocomplete="new-password"><button id="fileToggleBtn" class="btn-secondary small" type="button" aria-pressed="false" aria-label="Passwort anzeigen">Anzeigen</button><button id="fileGenerateBtn" class="btn-secondary small" type="button">Generieren</button></div><div class="strength-wrap"><div class="strength-bar"><div id="fileStrengthBar" class="strength-fill"></div></div><div id="fileStrengthText" class="strength-text">Passwortstärke: noch nicht eingegeben</div></div>
		  <div id="fileStatus" class="status" aria-live="polite"></div>
		  </div>
          <div class="field"><label for="filePassword2">Passwort bestätigen</label><input id="filePassword2" type="password" placeholder="Passwort erneut eingeben" autocomplete="new-password"></div>
          <div class="actions"><button id="encryptFileBtn" class="btn-primary">Datei verschlüsseln und herunterladen</button><button id="clearFileBtn" class="btn-danger">Löschen</button></div>
          
		  <div id="fileMeta" class="meta hidden"></div>
        </div>
      </div>
    </div>

    <div id="decrypt" class="panel">
      <div class="grid">
        <div>
          <div class="field"><label for="encryptedFile">Verschlüsselte Datei</label><input id="encryptedFile" type="file" accept=".pvault,.json,application/json,text/plain"><div class="help">Funktioniert mit Encryption 1-Textdateien und Encryption 1-Dateipaketen.</div></div>
          <div class="field"><label for="decryptPassword">Passwort / Passphrase</label><input id="decryptPassword" type="password" placeholder="Geben Sie dasselbe Passwort ein, das zum Verschlüsseln verwendet wurde" autocomplete="current-password"></div>
          <div class="actions"><button id="decryptBtn" class="btn-primary">Entschlüsseln</button><button id="clearDecryptBtn" class="btn-danger">Löschen</button></div>
          <div id="decryptStatus" class="status" aria-live="polite"></div><div id="decryptMeta" class="meta hidden"></div>
        </div>
        <div>
          <div id="textDecryptArea" class="field"><label for="decryptedText">Entschlüsselter Text</label><textarea id="decryptedText" placeholder="Der entschlüsselte Text erscheint hier..." spellcheck="false" rows="13"></textarea></div>
          <div id="textDecryptActions" class="actions"><button id="copyPlainBtn" class="btn-secondary" disabled>Text kopieren</button><button id="downloadPlainBtn" class="btn-secondary" disabled>Als .txt herunterladen</button></div>
          <div id="fileDecryptArea" class="meta hidden"></div>
        </div>
      </div>
    </div>
  </div>
<footer>
  <div class="footer">🔐 <strong>Encryption 1</strong> © 1996-<span id="year"></span> von Seán L. Young. Alle Rechte vorbehalten<br />Kostenlos nutzbar • Kein Konto nötig • Offline-fähig • <a href="EC-1.zip">ZIP herunterladen</a> • <a href="datenschutz.html">Datenschutz</a> • <a href="nutzungsbedingungen.html">Nutzungsbedingungen</a></div>
</footer>
<script>
  document.getElementById("year").textContent = new Date().getFullYear();
</script>
<div id="browserStatus" class="status" aria-live="polite"></div>
</div>
<script>
'use strict';
const APP_VERSION = 2.0;
const PBKDF2_ITERATIONS = 250000;
const SALT_LENGTH = 16, IV_LENGTH = 12, MAX_FILE_SIZE = 10 * 1024 * 1024;
const encoder = new TextEncoder(), decoder = new TextDecoder();
const $ = id => document.getElementById(id);
const els = {
 tabs: document.querySelectorAll('.tab-btn'), panels: document.querySelectorAll('.panel'),
 plainText:$('plainText'), textPassword:$('textPassword'), textPassword2:$('textPassword2'), textToggleBtn:$('textToggleBtn'), textGenerateBtn:$('textGenerateBtn'), textFilename:$('textFilename'), encryptTextBtn:$('encryptTextBtn'), clearTextBtn:$('clearTextBtn'), textStatus:$('textStatus'), textMeta:$('textMeta'), textStrengthBar:$('textStrengthBar'), textStrengthText:$('textStrengthText'),
 dropzone:$('dropzone'), fileInput:$('fileInput'), fileInfo:$('fileInfo'), filePassword:$('filePassword'), filePassword2:$('filePassword2'), fileToggleBtn:$('fileToggleBtn'), fileGenerateBtn:$('fileGenerateBtn'), encryptFileBtn:$('encryptFileBtn'), clearFileBtn:$('clearFileBtn'), fileStatus:$('fileStatus'), fileMeta:$('fileMeta'), fileStrengthBar:$('fileStrengthBar'), fileStrengthText:$('fileStrengthText'),
 encryptedFile:$('encryptedFile'), decryptPassword:$('decryptPassword'), decryptBtn:$('decryptBtn'), clearDecryptBtn:$('clearDecryptBtn'), decryptStatus:$('decryptStatus'), decryptMeta:$('decryptMeta'), decryptedText:$('decryptedText'), copyPlainBtn:$('copyPlainBtn'), downloadPlainBtn:$('downloadPlainBtn'), textDecryptArea:$('textDecryptArea'), textDecryptActions:$('textDecryptActions'), fileDecryptArea:$('fileDecryptArea'), browserStatus:$('browserStatus')
};
let selectedFile = null, currentDecryptedText = '';
// True only while the clipboard holds a password this app generated, so Clear
// can wipe it without touching anything the user copied from elsewhere.
let generatedPasswordInClipboard = false;
function setStatus(el,msg,type=''){el.textContent=msg;el.className='status'+(type?' '+type:'')}
function showMeta(el,html){el.innerHTML=html;el.classList.remove('hidden')}
function hide(el){el.classList.add('hidden')}
function uint8ToBase64(bytes){let binary='';const chunk=0x8000;for(let i=0;i<bytes.length;i+=chunk)binary+=String.fromCharCode(...bytes.subarray(i,i+chunk));return btoa(binary)}
function base64ToUint8(base64){const binary=atob(base64);const bytes=new Uint8Array(binary.length);for(let i=0;i<binary.length;i++)bytes[i]=binary.charCodeAt(i);return bytes}
function sanitizeFilename(name){const cleaned=(name||'private-note.pvault').trim().replace(/[\\/:*?"<>|]+/g,'_');return cleaned||'private-note.pvault'}
function ensurePvaultExtension(name){return /\.pvault$/i.test(name)?name:name+'.pvault'}
function formatBytes(n){if(n<1024)return n+' B';if(n<1024*1024)return (n/1024).toFixed(1)+' KB';return (n/(1024*1024)).toFixed(2)+' MB'}
async function deriveKey(password,salt,usages){const material=await crypto.subtle.importKey('raw',encoder.encode(password),'PBKDF2',false,['deriveKey']);return crypto.subtle.deriveKey({name:'PBKDF2',salt,iterations:PBKDF2_ITERATIONS,hash:'SHA-256'},material,{name:'AES-GCM',length:256},false,usages)}
async function encryptBytes(bytes,password,kind,extra={}){const salt=crypto.getRandomValues(new Uint8Array(SALT_LENGTH)),iv=crypto.getRandomValues(new Uint8Array(IV_LENGTH));const key=await deriveKey(password,salt,['encrypt']);const encrypted=await crypto.subtle.encrypt({name:'AES-GCM',iv},key,bytes);return {version:APP_VERSION,app:'Encryption 1',format:kind==='file'?'E1A-FILE-1':'E1-TEXT-1',cipher:'AES-GCM',kdf:'PBKDF2',hash:'SHA-256',iterations:PBKDF2_ITERATIONS,salt:uint8ToBase64(salt),iv:uint8ToBase64(iv),data:uint8ToBase64(new Uint8Array(encrypted)),createdAt:new Date().toISOString(),...extra}}
async function decryptBytes(payload,password){validatePayload(payload);const salt=base64ToUint8(payload.salt),iv=base64ToUint8(payload.iv),data=base64ToUint8(payload.data);const key=await deriveKey(password,salt,['decrypt']);return new Uint8Array(await crypto.subtle.decrypt({name:'AES-GCM',iv},key,data))}
function validatePayload(p){if(!p||typeof p!=='object')throw Error('Ungültiges verschlüsseltes Dateiformat.');for(const k of ['version','cipher','kdf','salt','iv','data'])if(!(k in p))throw Error('Fehlendes Feld in der verschlüsselten Datei: '+k);if(p.cipher!=='AES-GCM'||p.kdf!=='PBKDF2')throw Error('Nicht unterstütztes Verschlüsselungsformat.');if(p.iterations!==PBKDF2_ITERATIONS)throw Error('Nicht unterstützte PBKDF2-Iterationsanzahl.');}
function evaluatePasswordStrength(password){const c={length8:password.length>=8,uppercase:/[A-Z]/.test(password),special:/[^A-Za-z0-9]/.test(password),lowercase:/[a-z]/.test(password),number:/\d/.test(password),length12:password.length>=12,length16:password.length>=16};let score=0;if(c.length8)score+=30;if(c.uppercase)score+=20;if(c.special)score+=20;if(c.lowercase)score+=10;if(c.number)score+=10;if(c.length12)score+=5;if(c.length16)score+=5;let label='Sehr schwach',color='#b42318';if(!password){label='Noch nicht eingegeben';color='#d0d7e2';score=0}else if(score>=90){label='Sehr stark';color='#157347'}else if(score>=70){label='Stark';color='#2d8a57'}else if(score>=50){label='Mittel';color='#b7791f'}else if(score>=30){label='Schwach';color='#c05621'}return{score,label,color,checks:c,meetsMinimum:c.length8&&c.uppercase&&c.special}}
function updateStrength(input,bar,text){const r=evaluatePasswordStrength(input.value);bar.style.width=r.score+'%';bar.style.background=r.color;const missing=[];if(!r.checks.length8)missing.push('mindestens 8 Zeichen');if(!r.checks.uppercase)missing.push('einen Großbuchstaben');if(!r.checks.special)missing.push('ein Sonderzeichen');text.textContent=!input.value?'Passwortstärke: noch nicht eingegeben':r.meetsMinimum?'Passwortstärke: '+r.label+'. Mindestanforderung erfüllt.':'Passwortstärke: '+r.label+'. Noch erforderlich: '+missing.join(', ')+'.';return r}
function downloadBlob(filename,blob){const url=URL.createObjectURL(blob),a=document.createElement('a');a.href=url;a.download=filename;document.body.appendChild(a);a.click();a.remove();setTimeout(()=>URL.revokeObjectURL(url),1000)}
function downloadPayload(filename,payload){downloadBlob(filename,new Blob([JSON.stringify(payload,null,2)],{type:'application/json'}))}
function checkSecure(){return !!window.crypto?.subtle}
function setBrowserCompatibilityStatus(){const secure=!!window.isSecureContext,cryptoSupported=!!window.crypto?.subtle,supported=secure&&cryptoSupported;if(!supported){setStatus(els.browserStatus,'Dieser Browser ist zu alt oder läuft nicht in einem sicheren Kontext, der für die Verschlüsselung erforderlich ist. Bitte aktualisieren Sie Ihren Browser oder öffnen Sie diese Seite in einem neueren.','error');[els.encryptTextBtn,els.encryptFileBtn,els.decryptBtn].forEach(b=>b.disabled=true)}else{setStatus(els.browserStatus,'Browser-Kompatibilität: Bestanden','success')}}
function switchTab(name){els.tabs.forEach(b=>b.classList.toggle('active',b.dataset.tab===name));els.panels.forEach(p=>p.classList.toggle('active',p.id===name))}
function validPassword(p1,p2){if(!p1)return 'Bitte geben Sie ein Passwort ein.';if(p1!==p2)return 'Die Passwörter stimmen nicht überein.';if(!evaluatePasswordStrength(p1).meetsMinimum)return 'Passwortanforderungen: mindestens 8 Zeichen, darunter mindestens ein Großbuchstabe und ein Sonderzeichen.';return ''}
function generateStrongPassword(length=24){const alphabet='ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*-_=+';const random=new Uint32Array(length);crypto.getRandomValues(random);let result='';for(let i=0;i<random.length;i++)result+=alphabet[random[i]%alphabet.length];return result}
function setFieldVisibility(input,toggleBtn,visible){input.type=visible?'text':'password';toggleBtn.textContent=visible?'Verbergen':'Anzeigen';toggleBtn.setAttribute('aria-pressed',String(visible));toggleBtn.setAttribute('aria-label',visible?'Passwort verbergen':'Passwort anzeigen')}
async function generateInto(input,input2,toggleBtn,statusEl,strengthFn){const password=generateStrongPassword();input.value=password;input2.value=password;setFieldVisibility(input,toggleBtn,true);strengthFn();input.focus();input.select();if(!navigator.clipboard?.writeText){setStatus(statusEl,"Passwort generiert und oben angezeigt. Dieser Browser unterstützt kein automatisches Kopieren in die Zwischenablage. Bitte kopieren Sie es manuell, bevor Sie verschlüsseln.",'error');return}try{await navigator.clipboard.writeText(password);generatedPasswordInClipboard=true;setStatus(statusEl,'Passwort generiert und in die Zwischenablage kopiert. Speichern Sie es jetzt an einem sicheren Ort – es kann später nicht wiederhergestellt werden. „Löschen“ entfernt es auch aus der Zwischenablage.','success')}catch{setStatus(statusEl,'Passwort generiert und oben angezeigt. Das automatische Kopieren in die Zwischenablage ist fehlgeschlagen – bitte kopieren Sie es manuell, bevor Sie verschlüsseln.','error')}}
function clearGeneratedClipboard(){if(generatedPasswordInClipboard&&navigator.clipboard?.writeText){navigator.clipboard.writeText('').catch(()=>{});generatedPasswordInClipboard=false}}

els.tabs.forEach(b=>b.addEventListener('click',()=>switchTab(b.dataset.tab)));
els.textPassword.addEventListener('input',()=>updateStrength(els.textPassword,els.textStrengthBar,els.textStrengthText));
els.filePassword.addEventListener('input',()=>updateStrength(els.filePassword,els.fileStrengthBar,els.fileStrengthText));
els.textToggleBtn.addEventListener('click',()=>{setFieldVisibility(els.textPassword,els.textToggleBtn,els.textPassword.type==='password');els.textPassword.focus()});
els.fileToggleBtn.addEventListener('click',()=>{setFieldVisibility(els.filePassword,els.fileToggleBtn,els.filePassword.type==='password');els.filePassword.focus()});
els.textGenerateBtn.addEventListener('click',()=>generateInto(els.textPassword,els.textPassword2,els.textToggleBtn,els.textStatus,()=>updateStrength(els.textPassword,els.textStrengthBar,els.textStrengthText)));
els.fileGenerateBtn.addEventListener('click',()=>generateInto(els.filePassword,els.filePassword2,els.fileToggleBtn,els.fileStatus,()=>updateStrength(els.filePassword,els.fileStrengthBar,els.fileStrengthText)));

els.encryptTextBtn.addEventListener('click',async()=>{hide(els.textMeta);setStatus(els.textStatus,'');if(!checkSecure()){setStatus(els.textStatus,'Dieser Browserkontext stellt die erforderliche Web-Crypto-API nicht bereit.','error');return}const text=els.plainText.value,p=els.textPassword.value,p2=els.textPassword2.value,err=validPassword(p,p2);if(!text.trim())return setStatus(els.textStatus,'Bitte geben Sie Text zum Verschlüsseln ein.','error');if(err)return setStatus(els.textStatus,err,'error');try{els.encryptTextBtn.disabled=true;setStatus(els.textStatus,'Lokale Verschlüsselung läuft...','success');const payload=await encryptBytes(encoder.encode(text),p,'text');downloadPayload(ensurePvaultExtension(sanitizeFilename(els.textFilename.value)),payload);showMeta(els.textMeta,'<strong>Verschlüsselung abgeschlossen.</strong><br>Der Text wurde lokal verschlüsselt und die verschlüsselte Datei wurde heruntergeladen.');setStatus(els.textStatus,'Verschlüsselung abgeschlossen.','success')}catch(e){setStatus(els.textStatus,'Verschlüsselung fehlgeschlagen: '+(e.message||e),'error')}finally{els.encryptTextBtn.disabled=false}});

function selectFile(file){if(!file)return;if(file.size>MAX_FILE_SIZE){selectedFile=null;hide(els.fileInfo);setStatus(els.fileStatus,'Diese Datei ist '+formatBytes(file.size)+' groß. Encryption 1 unterstützt Dateien bis zu 10 MB.','error');return}selectedFile=file;els.fileInfo.innerHTML='<strong>'+escapeHtml(file.name)+'</strong><br>'+formatBytes(file.size)+(file.type?' • '+escapeHtml(file.type):'');els.fileInfo.classList.remove('hidden');setStatus(els.fileStatus,'Datei bereit zur Verschlüsselung.','success')}
function escapeHtml(s){return String(s).replace(/[&<>'"]/g,c=>({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c]))}
els.fileInput.addEventListener('change',()=>selectFile(els.fileInput.files[0]));els.dropzone.addEventListener('click',()=>els.fileInput.click());els.dropzone.addEventListener('keydown',e=>{if(e.key==='Enter'||e.key===' ')els.fileInput.click()});['dragenter','dragover'].forEach(ev=>els.dropzone.addEventListener(ev,e=>{e.preventDefault();els.dropzone.classList.add('dragover')}));['dragleave','drop'].forEach(ev=>els.dropzone.addEventListener(ev,e=>{e.preventDefault();els.dropzone.classList.remove('dragover')}));els.dropzone.addEventListener('drop',e=>selectFile(e.dataTransfer.files[0]));

els.encryptFileBtn.addEventListener('click',async()=>{hide(els.fileMeta);if(!checkSecure()){setStatus(els.fileStatus,'Dieser Browserkontext stellt die erforderliche Web-Crypto-API nicht bereit.','error');return}if(!selectedFile)return setStatus(els.fileStatus,'Bitte wählen Sie zuerst eine Datei aus.','error');const p=els.filePassword.value,p2=els.filePassword2.value,err=validPassword(p,p2);if(err)return setStatus(els.fileStatus,err,'error');try{els.encryptFileBtn.disabled=true;setStatus(els.fileStatus,'Lokales Lesen und Verschlüsseln läuft...','success');const bytes=new Uint8Array(await selectedFile.arrayBuffer());const container=encoder.encode(JSON.stringify({filename:selectedFile.name,mimeType:selectedFile.type||'application/octet-stream',size:selectedFile.size,data:uint8ToBase64(bytes)}));const payload=await encryptBytes(container,p,'file');downloadPayload(sanitizeFilename(selectedFile.name)+'.pvault',payload);showMeta(els.fileMeta,'<strong>Verschlüsselung abgeschlossen.</strong><br>Originaldatei: <span class="mono">'+escapeHtml(selectedFile.name)+'</span><br>Größe: '+formatBytes(selectedFile.size)+'<br>Das verschlüsselte Paket wurde lokal heruntergeladen.');setStatus(els.fileStatus,'Datei erfolgreich verschlüsselt.','success')}catch(e){setStatus(els.fileStatus,'Verschlüsselung fehlgeschlagen: '+(e.message||e),'error')}finally{els.encryptFileBtn.disabled=false}});

els.decryptBtn.addEventListener('click',async()=>{hide(els.decryptMeta);hide(els.fileDecryptArea);els.decryptedText.value='';els.textDecryptArea.classList.remove('hidden');els.textDecryptActions.classList.remove('hidden');els.copyPlainBtn.disabled=true;els.downloadPlainBtn.disabled=true;currentDecryptedText='';if(!checkSecure()){setStatus(els.decryptStatus,'Dieser Browserkontext stellt die erforderliche Web-Crypto-API nicht bereit.','error');return}const file=els.encryptedFile.files[0],p=els.decryptPassword.value;if(!file)return setStatus(els.decryptStatus,'Bitte wählen Sie eine verschlüsselte .pvault-Datei aus.','error');if(!p)return setStatus(els.decryptStatus,'Bitte geben Sie das Passwort ein.','error');try{els.decryptBtn.disabled=true;setStatus(els.decryptStatus,'Lokale Entschlüsselung läuft...','success');const payload=JSON.parse(await file.text()),bytes=await decryptBytes(payload,p);if(payload.format==='E1A-FILE-1'){const container=JSON.parse(decoder.decode(bytes));if(!container.filename||!container.data)throw Error('Das verschlüsselte Dateipaket ist ungültig.');const original=base64ToUint8(container.data);const blob=new Blob([original],{type:container.mimeType||'application/octet-stream'});downloadBlob(sanitizeFilename(container.filename),blob);els.textDecryptArea.classList.add('hidden');els.textDecryptActions.classList.add('hidden');els.fileDecryptArea.innerHTML='<strong>Datei erfolgreich entschlüsselt.</strong><br>Wiederhergestellt: <span class="mono">'+escapeHtml(container.filename)+'</span><br>Größe: '+formatBytes(original.byteLength)+'<br><button id="downloadRestored" class="btn-primary" style="margin-top:12px">Wiederhergestellte Datei erneut herunterladen</button>';els.fileDecryptArea.classList.remove('hidden');$('downloadRestored').addEventListener('click',()=>downloadBlob(sanitizeFilename(container.filename),new Blob([original],{type:container.mimeType||'application/octet-stream'})));showMeta(els.decryptMeta,'<strong>Dateipaket entschlüsselt.</strong><br>Die Originaldatei wurde lokal wiederhergestellt und heruntergeladen.');setStatus(els.decryptStatus,'Entschlüsselung erfolgreich.','success')}else{const text=decoder.decode(bytes);currentDecryptedText=text;els.decryptedText.value=text;els.copyPlainBtn.disabled=false;els.downloadPlainBtn.disabled=false;showMeta(els.decryptMeta,'<strong>Text erfolgreich entschlüsselt.</strong><br>Dies kann ein älteres Encryption 1-Textpaket oder ein Advanced-Textpaket sein.');setStatus(els.decryptStatus,'Entschlüsselung erfolgreich.','success')}}catch(e){setStatus(els.decryptStatus,'Entschlüsselung fehlgeschlagen. Möglicherweise ist das Passwort falsch, die Datei beschädigt oder das Dateiformat nicht unterstützt.','error')}finally{els.decryptBtn.disabled=false}});
els.copyPlainBtn.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(currentDecryptedText);generatedPasswordInClipboard=false;setStatus(els.decryptStatus,'Entschlüsselter Text in die Zwischenablage kopiert.','success')}catch{setStatus(els.decryptStatus,'Text konnte in diesem Browser nicht in die Zwischenablage kopiert werden.','error')}});
els.downloadPlainBtn.addEventListener('click',()=>{if(currentDecryptedText)downloadBlob('decrypted-text.txt',new Blob([currentDecryptedText],{type:'text/plain;charset=utf-8'}))});
function clearText(){els.plainText.value='';els.textPassword.value='';els.textPassword2.value='';els.textFilename.value='private-note.pvault';setFieldVisibility(els.textPassword,els.textToggleBtn,false);updateStrength(els.textPassword,els.textStrengthBar,els.textStrengthText);hide(els.textMeta);setStatus(els.textStatus,'');clearGeneratedClipboard()}
function clearFile(){selectedFile=null;els.fileInput.value='';els.filePassword.value='';els.filePassword2.value='';hide(els.fileInfo);hide(els.fileMeta);setFieldVisibility(els.filePassword,els.fileToggleBtn,false);updateStrength(els.filePassword,els.fileStrengthBar,els.fileStrengthText);setStatus(els.fileStatus,'');clearGeneratedClipboard()}
function clearDecrypt(){els.encryptedFile.value='';els.decryptPassword.value='';els.decryptedText.value='';currentDecryptedText='';els.copyPlainBtn.disabled=true;els.downloadPlainBtn.disabled=true;hide(els.decryptMeta);hide(els.fileDecryptArea);els.textDecryptArea.classList.remove('hidden');els.textDecryptActions.classList.remove('hidden');setStatus(els.decryptStatus,'');clearGeneratedClipboard()}
els.clearTextBtn.addEventListener('click',clearText);els.clearFileBtn.addEventListener('click',clearFile);els.clearDecryptBtn.addEventListener('click',clearDecrypt);
updateStrength(els.textPassword,els.textStrengthBar,els.textStrengthText);updateStrength(els.filePassword,els.fileStrengthBar,els.fileStrengthText);
setBrowserCompatibilityStatus();
if(!checkSecure()){setStatus(els.textStatus,'Web Crypto ist in diesem Browserkontext nicht verfügbar.','error');setStatus(els.fileStatus,'Web Crypto ist in diesem Browserkontext nicht verfügbar.','error');setStatus(els.decryptStatus,'Web Crypto ist in diesem Browserkontext nicht verfügbar.','error');[els.encryptTextBtn,els.encryptFileBtn,els.decryptBtn].forEach(b=>b.disabled=true)}
</script>



<script src='https://storage.ko-fi.com/cdn/scripts/overlay-widget.js'></script>
<script>
  kofiWidgetOverlay.draw('youngslanguageconsulting', {
    'type': 'floating-chat',
    'floating-chat.donateButton.text': 'EC-1 unterstützen',
    'floating-chat.donateButton.background-color': '#d5eef6',
    'floating-chat.donateButton.text-color': '#323842'
  });
</script>

</body>
</html>