Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Passwort-Manager-App in React Native

Passwort-Manager-App

~12 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026

Ein Passwort-Manager demonstriert VERSCHLÜSSELTE Speicherung (wichtig, siehe Kapitel "AsyncStorage", wo ausdrücklich erwähnt wird, dass AsyncStorage NICHT für sensible Daten geeignet ist), eine Passwort-Generator-Funktion und Sichtbar-/Verbergen-Umschaltung.

1. Beschreibung

expo-secure-store nutzt die native Keychain (iOS) bzw. den Keystore (Android) – verschlüsselte, sichere Speicherorte, komplett getrennt vom normalen (unverschlüsselten) AsyncStorage. Die API ist bewusst fast identisch zu AsyncStorage gehalten (setItemAsync/getItemAsync statt setItem/getItem).

2. Kurzes Beispiel

import * as SecureStore from 'expo-secure-store';

await SecureStore.setItemAsync('master_passwort', 'geheim123');
const wert = await SecureStore.getItemAsync('master_passwort');

3. Komplettes Projekt: Passwort-Tresor

npx create-expo-app password-manager-app
cd password-manager-app
npx expo install expo-secure-store
utils/passwortGenerator.js
const ZEICHENSATZ =
  'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';

export function generierePasswort(laenge = 16) {
  let ergebnis = '';
  for (let i = 0; i < laenge; i++) {
    const zufallsIndex = Math.floor(Math.random() * ZEICHENSATZ.length);
    ergebnis += ZEICHENSATZ[zufallsIndex];
  }
  return ergebnis;
}
App.js
import { useState, useEffect } from 'react';
import {
  View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet,
} from 'react-native';
import * as SecureStore from 'expo-secure-store';
import { generierePasswort } from './utils/passwortGenerator';

const SPEICHER_SCHLUESSEL = 'gespeicherte_eintraege';

export default function App() {
  const [eintraege, setEintraege] = useState([]);
  const [seite, setSeite] = useState('');
  const [passwort, setPasswort] = useState('');
  const [sichtbar, setSichtbar] = useState(false);

  useEffect(() => {
    SecureStore.getItemAsync(SPEICHER_SCHLUESSEL).then((gespeichert) => {
      if (gespeichert) setEintraege(JSON.parse(gespeichert));
    });
  }, []);

  async function speichern(neueEintraege) {
    setEintraege(neueEintraege);
    await SecureStore.setItemAsync(SPEICHER_SCHLUESSEL, JSON.stringify(neueEintraege));
  }

  function eintragHinzufuegen() {
    if (!seite.trim() || !passwort.trim()) return;
    const neuerEintrag = { id: Date.now(), seite: seite.trim(), passwort };
    speichern([neuerEintrag, ...eintraege]);
    setSeite('');
    setPasswort('');
  }

  function eintragLoeschen(id) {
    speichern(eintraege.filter((e) => e.id !== id));
  }

  return (
    <View style={styles.container}>
      <Text style={styles.titel}>Passwort-Tresor</Text>

      <TextInput
        style={styles.input}
        placeholder="Website / Dienst"
        value={seite}
        onChangeText={setSeite}
      />

      <View style={styles.passwortZeile}>
        <TextInput
          style={[styles.input, styles.passwortInput]}
          placeholder="Passwort"
          value={passwort}
          onChangeText={setPasswort}
          secureTextEntry={!sichtbar}
        />
        <TouchableOpacity onPress={() => setSichtbar((s) => !s)} style={styles.augeButton}>
          <Text>{sichtbar ? '????' : '????'}</Text>
        </TouchableOpacity>
      </View>

      <TouchableOpacity
        style={styles.generierenButton}
        onPress={() => setPasswort(generierePasswort(16))}
      >
        <Text style={styles.generierenText}>Sicheres Passwort generieren</Text>
      </TouchableOpacity>

      <TouchableOpacity style={styles.speichernButton} onPress={eintragHinzufuegen}>
        <Text style={styles.speichernText}>Eintrag speichern</Text>
      </TouchableOpacity>

      <FlatList
        style={styles.liste}
        data={eintraege}
        keyExtractor={(item) => String(item.id)}
        ListEmptyComponent={<Text style={styles.leer}>Noch keine Einträge gespeichert.</Text>}
        renderItem={({ item }) => (
          <View style={styles.eintragZeile}>
            <View>
              <Text style={styles.eintragSeite}>{item.seite}</Text>
              <Text style={styles.eintragPasswort}>{'•'.repeat(item.passwort.length)}</Text>
            </View>
            <TouchableOpacity onPress={() => eintragLoeschen(item.id)}>
              <Text style={styles.loeschen}>✕</Text>
            </TouchableOpacity>
          </View>
        )}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, paddingTop: 60, paddingHorizontal: 20 },
  titel: { fontSize: 22, fontWeight: 'bold', marginBottom: 16 },
  input: { borderWidth: 1, borderColor: '#d1d5db', borderRadius: 8, padding: 10, marginBottom: 10 },
  passwortZeile: { flexDirection: 'row', alignItems: 'center' },
  passwortInput: { flex: 1 },
  augeButton: { padding: 10 },
  generierenButton: { paddingVertical: 8, marginBottom: 10 },
  generierenText: { color: '#2563eb' },
  speichernButton: { backgroundColor: '#2563eb', padding: 12, borderRadius: 8, alignItems: 'center', marginBottom: 20 },
  speichernText: { color: 'white', fontWeight: 'bold' },
  liste: { flex: 1 },
  leer: { textAlign: 'center', color: '#9ca3af', marginTop: 20 },
  eintragZeile: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#e5e7eb' },
  eintragSeite: { fontSize: 16, fontWeight: 'bold' },
  eintragPasswort: { color: '#6b7280', marginTop: 2 },
  loeschen: { color: '#dc2626', fontSize: 16, paddingHorizontal: 8 },
});

4. Erklärung

  • SecureStore statt AsyncStorage für die GESAMTE Eintragsliste – nicht nur einzelne Passwörter, sondern der komplette JSON-Datensatz landet verschlüsselt, da er sensible Daten enthält.
  • In der Liste wird das Passwort NIE im Klartext angezeigt ('•'.repeat(...)) – nur beim Erstellen im Eingabefeld ist es über den Augen-Button kurzzeitig sichtbar zu machen, ein gängiges Sicherheits-UX-Muster.
  • generierePasswort() nutzt Math.random() – für ein Demo-/Lernprojekt ausreichend, für eine ECHTE Produktions-App wäre ein kryptographisch sicherer Zufallsgenerator (expo-crypto) vorzuziehen, da Math.random() nicht als sicherheitsrelevant gilt.
  • speichern() aktualisiert State UND SecureStore in EINER Funktion – verhindert, dass beide je auseinanderlaufen (State zeigt etwas anderes als tatsächlich gespeichert ist).

5. Ausgaben

Ausgabe
Titel "Passwort-Tresor", Eingabefelder für Website und Passwort (mit Augen-Symbol zum Ein-/Ausblenden), Link "Sicheres Passwort generieren" (füllt das Passwortfeld mit 16 zufälligen Zeichen), blauer "Eintrag speichern"-Button. Darunter die Liste gespeicherter Einträge, Website fett, Passwort als Punkte maskiert, roter "✕" zum Löschen. Nach App-Neustart bleiben alle Einträge erhalten.