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-storeconst 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;
}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
SecureStorestattAsyncStoragefü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()nutztMath.random()– für ein Demo-/Lernprojekt ausreichend, für eine ECHTE Produktions-App wäre ein kryptographisch sicherer Zufallsgenerator (expo-crypto) vorzuziehen, daMath.random()nicht als sicherheitsrelevant gilt.speichern()aktualisiert State UNDSecureStorein EINER Funktion – verhindert, dass beide je auseinanderlaufen (State zeigt etwas anderes als tatsächlich gespeichert ist).