10.5 Jelszavak és érzékeny adatok kezelése
Áttekintő
Minden héten hallani arról, hogy valamelyik cég adatbázisát feltörték, és kiszivárgott felhasználók millióinak jelszava. Az esetek nagy részében az derül ki: a cég nem megfelelően tárolta a jelszavakat. Nem arról van szó, hogy hiányzott a tűzfal, vagy valamilyen exotikus exploit élt – egyszerűen csak rosszul csinálták az alapokat.
Ez az alfejezet arról szól, hogy miért nem elég a titkosítás, hogyan kell jelszavakat valóban biztonságosan tárolni, és hogyan kezeld az érzékeny adatokat (API kulcsok, felhasználói adatok, személyes információk) a mindennapi fejlesztésben. Nem kell security expertnek lenned – de ezeket az alapokat egy cég elvárja tőled az első munkanapodtól.
Amit ebben az alfejezetben megtanulsz:
- Miért plaintext és MD5 jelszótárolás időzített bomba
- Mi a különbség a titkosítás és a hashing között, és miért számít
- Hogyan működik a bcrypt/argon2 (anélkül, hogy kriptográfus legyél)
- Miért kerül a
.envfájl a.gitignore-ba, és mi történik, ha nem kerül oda - Hogyan kezeld a PII (személyes azonosításra alkalmas) adatokat felelősen
Részletes leírás
A jelszótárolás négy szintje – és melyik hol áll
Képzelj el egy skálát, amelyen a „teljesen felelőtlen" és a „production-ready" két végpont:
VESZÉLYES BIZTONSÁGOS
| |
plaintext → MD5/SHA1 → SHA256 (só nélkül) → bcrypt/argon2
Nézzük végig, miért nem elég az első három.
1. Plaintext tárolás – az alapvető hiba
# ❌ SOHA NE CSINÁLD EZT
user = {
"email": "user@example.com",
"password": "titkos123" # plaintext a DB-ben
}
Ha valaki megszerzi az adatbázist (SQL injection, backup kiszivárgás, bennfentes hozzáférés), azonnal hozzáfér minden jelszóhoz. És mivel az emberek sok helyen ugyanazt a jelszót használják, ez nemcsak a te rendszered – hanem a felhasználó bankja, e-mailje és minden más fiókja is kompromittálódik.
2. MD5 és SHA1 – már nem elég
Az MD5 és SHA1 gyors hash algoritmusok. Ez éppen a probléma. Egy modern GPU másodpercenként több milliárd MD5 hash-t képes kiszámolni. Ez azt jelenti, hogy a teljes „közönséges jelszó" teret néhány perc alatt le lehet fésülni.
import hashlib
# ❌ SHA1 – ugyanolyan problémás, mint az MD5
password_hash = hashlib.sha1("titkos123".encode()).hexdigest()
# e36e7b3e... – de rainbow table-ből másodpercek alatt visszafejtik
A rainbow table egy előre kiszámolt hash-adatbázis. Feltöltöd a hash-t, visszakapod a jelszót. Vannak publikus rainbow table-ök, amelyek a legtöbb MD5 hash-t azonnal visszafejtik.
3. Miért kell a só (salt)?
A só egy véletlenszerű karakterlánc, amelyet a jelszóhoz fűznek hash előtt:
jelszó: "titkos123"
só: "x7kQ9m"
hash inputja: "titkos123x7kQ9m"
Ezzel két dolgot érünk el:
- Két felhasználó ugyanolyan jelszava különböző hash-t kap (mert különböző sót kapnak)
- Rainbow table-ök hatástalanok (mert az adott sóval előre kiszámolt tábla nem létezik)
A só nem titok – tárolhatod ugyanabban az adatbázisban, ahol a hash van. Szerepe nem a titkosság, hanem az egyediesítés.
4. bcrypt és argon2 – a helyes megoldás
A bcrypt és az argon2 szándékosan lassú hash algoritmusok. Ez az ő lényegük.
import bcrypt
# ✅ Jelszó hash-elése bcrypt-tel
password = "titkos123".encode()
salt = bcrypt.gensalt() # automatikusan generál sót
hashed = bcrypt.hashpw(password, salt)
# ✅ Jelszó ellenőrzése (bejelentkezéskor)
is_correct = bcrypt.checkpw(password, hashed)
const bcrypt = require('bcrypt');
// ✅ JavaScript/Node.js
const saltRounds = 12; // a „cost factor" – minél nagyobb, annál lassabb
const hashed = await bcrypt.hash('titkos123', saltRounds);
const isCorrect = await bcrypt.compare('titkos123', hashed);
// ✅ PHP – beépített password_hash()
$hashed = password_hash('titkos123', PASSWORD_BCRYPT);
// Ellenőrzés
$isCorrect = password_verify('titkos123', $hashed);
A cost factor (work factor) szabályozza, mennyire lassú a hash. Ha 12-t használsz, a bcrypt kb. 250 ms-t tölt hash-eléssel – egy felhasználónak ez nem feltűnő, de egy támadónak, aki milliószámra próbálkozik, óriási akadály.
Argon2 az újabb ajánlott algoritmus (a bcrypt-nél is erősebb), de bcrypt-tel is production-biztos rendszert lehet építeni.
Ökölszabály: Soha ne írj saját jelszóhash implementációt. Használd a platformod beépített, jól tesztelt könyvtárát.
A .env fájl és a gitignore – miért kritikus
Mi az a .env?
A .env egy fájl, amelybe az alkalmazás konfigurációs értékeit tesszük – különösen azokat, amelyeknek nem szabad a kódban lenniük:
# .env – SOHA NE KERÜLJÖN GIT-BE
DATABASE_URL=postgresql://user:password@localhost/myapp
JWT_SECRET=v3ry_s3cr3t_k3y_here
STRIPE_API_KEY=sk_live_xxxxxxxxxxx
SENDGRID_API_KEY=SG.xxxxxxxxxxxx
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/xxxx
Mi történik, ha Git-be kerül?
Ez az egyik leggyakoribb security incidens junior fejlesztőknél. A folyamat általában így néz ki:
- Junior fejlesztő létrehozza a
.envfájlt az API kulcsokkal - Lefuttatja a
git add .parancsot - Commitolja és pusholja GitHub-ra
- A repo public (vagy valaki hozzáfér)
- Automatizált botok perceken belül megtalálják és ellopják a kulcsokat
- AWS esetén: a bot virtuális gépeket indít, kriptobányászatra, te fizeted a számlát (akár több ezer dollár egy nap alatt)
Ez nem elmélet – ez rendszeresen megtörténik, és AWS/GitHub rendszeresen küldenek figyelmeztetőket kompromittált kulcsok esetén.
# .gitignore – ezt mindig add a projekthez
.env
.env.local
.env.*.local
*.pem
*.key
secrets/
Ha már kikerült – mit tegyél?
Ha véletlenül commitoltad a kulcsot:
- Azonnal von vissza (revoke) minden érintett API kulcsot – ez az első lépés, nem a git history tisztítása
- Ellenőrizd, volt-e jogosulatlan hozzáférés
- Tisztítsd meg a git history-t (
git filter-branchvagy BFG Repo Cleaner) - A history tisztítása után is: a régi kulcs már kompromittált, soha ne használd újra
.env.example – a helyes minta
# .env.example – ez KERÜL git-be, értékek nélkül
DATABASE_URL=postgresql://user:password@localhost/myapp
JWT_SECRET=change_me_to_a_random_string
STRIPE_API_KEY=your_stripe_key_here
A .env.example mutatja, milyen változókra van szükség, de nem tartalmaz valódi értékeket. Így új fejlesztő tudja, mit kell beállítania, de a titkok nem kerülnek nyilvánosságra.
HTTPS és adatátvitel biztonsága
Miért nem elég a HTTPS az adatbázisnál?
HTTPS védi az adatot átvitel közben – a felhasználó böngészője és a szervered között. De az adatbázisban tárolt jelszót nem védi. Ezért kell a hashing.
Felhasználó böngészője →[HTTPS]→ Szerver →[?]→ Adatbázis
(titkosítva) (itt kell bcrypt!)
Fejlesztési gondolkodásmód HTTPS-sel kapcsolatban
- Production-ban mindig HTTPS – ma már ingyenes (Let's Encrypt)
- Jelszavakat soha ne küldj HTTP-n – még teszteléskor sem érdemes megszokni
- API kulcsokat csak HTTPS-en küldj – header-ben, nem URL paraméterként (az URL-ek logokban jelennek meg)
# ❌ API kulcs URL-ben – megjelenik a logokban
GET /api/data?api_key=sk_live_xxxx
# ✅ API kulcs header-ben
GET /api/data
Authorization: Bearer sk_live_xxxx
PII – Személyes azonosításra alkalmas adatok
A PII (Personally Identifiable Information) olyan adat, amellyel egy személy azonosítható: név, e-mail cím, telefonszám, születési dátum, lakcím, IP cím, stb.
Miért számít a junior fejlesztőnek?
GDPR (EU) és más adatvédelmi törvények alapján a cégek felelősek a PII-ért. Ha te írod a kódot, ami PII-t kezel, te felszel felelős a helyes implementációért.
Alapelvek PII kezelésnél
1. Ne gyűjtsd, ha nem kell
# ❌ Felesleges adat gyűjtése
user_profile = {
"name": name,
"email": email,
"birth_date": birth_date, # Valóban szükséges?
"phone": phone, # Valóban szükséges?
"ip_address": ip_address # Valóban szükséges?
}
2. Ne logolj személyes adatot
# ❌ PII a logban
logger.info(f"User login: {user.email} with password {password}")
# ✅ Minimális logolás
logger.info(f"User login attempt: user_id={user.id}")
3. Maszkold, ha megjeleníteni kell
# ✅ Email maszkolása (pl. hibaüzenetben)
def mask_email(email):
parts = email.split("@")
return parts[0][:2] + "***@" + parts[1]
# "joska@example.com" → "jo***@example.com"
4. Ne tárold tovább, mint szükséges Ha felhasználó törli fiókját: az adatait is törölni kell (nem csak inactive-re állítani).
Életszerű példák
1. példa: Az „egyszerű" regisztrációs endpoint
Egy junior fejlesztő megkapja az első feladatát: implementálja a regisztrációt.
# ❌ Amit egy junior elsőre megírhat
@app.route('/register', methods=['POST'])
def register():
email = request.form['email']
password = request.form['password']
# Direkt tárolja a jelszót
user = User(email=email, password=password)
db.session.add(user)
db.session.commit()
return jsonify({"status": "ok"})
# ✅ Amit production-ban kell csinálni
import bcrypt
@app.route('/register', methods=['POST'])
def register():
email = request.form['email']
password = request.form['password'].encode('utf-8')
# Hash-eli a jelszót, mielőtt adatbázisba kerülne
hashed_password = bcrypt.hashpw(password, bcrypt.gensalt())
user = User(email=email, password_hash=hashed_password)
db.session.add(user)
db.session.commit()
return jsonify({"status": "ok"})
A változtatás: a jelszó soha nem kerül az adatbázisba – csak a hash.
2. példa: A véletlen Git push
Péter csatlakozik egy új csapathoz. Klónozza a repót, létrehozza a .env fájlt a fejlesztési kulcsokkal, majd pánikszerűen felfedezi:
git log --oneline
a1b2c3d Add environment configuration ← ez a commit tartalmazza a .env-t
# Mi történt?
git show a1b2c3d --stat
# .env fájl megjelenik a módosított fájlok között
Helyes reakció:
- Azonnal visszavonja (revoke) az összes kulcsot a szolgáltatóknál (AWS console, Stripe dashboard, stb.)
- Új kulcsokat generál
- Értesíti a csapatvezető/senior fejlesztőt
- Utána tisztítja meg a git history-t
Rossz reakció: Csak törli a fájlt és commitolja – a kulcs benne marad a history-ban.
3. példa: Hibaüzenet, ami túl sokat árul el
# ❌ Stack trace a felhasználónak – security anti-pattern
@app.errorhandler(500)
def server_error(e):
return str(e), 500
# Kimenet: "psycopg2.OperationalError: FATAL: password authentication failed
# for user 'myapp_user' at host 'db.internal.company.com:5432'"
Ez a hibaüzenet megmutatja:
- Az adatbázis belső host nevét és portját
- A felhasználónevet
- Hogy PostgreSQL-t használnak
# ✅ Felhasználónak: általános hibaüzenet; logban: a részletek
@app.errorhandler(500)
def server_error(e):
logger.error(f"Internal error: {str(e)}") # fejlesztőknek
return jsonify({"error": "Internal server error"}), 500 # felhasználónak
4. példa: A „csak fejlesztésre" mentalitás csapdája
# "Ezt csak fejlesztésre használom, majd production-ban megváltoztatom"
SECRET_KEY = "dev_secret"
DEBUG = True
DATABASE_URL = "postgresql://admin:admin@localhost/myapp"
Két probléma:
- A „majd production-ban" sokszor elmarad
- A fejlesztési szokások átvivődnek a production-ba
Jobb megközelítés: kezdettől fogva .env fájlból olvasni a konfigurációt.
Tesztfeladatok
1. feladat – Jelszótárolási módszerek rangsorolása
Sorold fel a következő jelszótárolási módszereket legveszélyesebbtől a legbiztonságosabbig:
- A) bcrypt (cost factor 12)
- B) SHA-256 só nélkül
- C) Plaintext
- D) MD5
- E) argon2id
Helyes sorrend: C → D → B → A → E
(plaintext → md5 → sha256 sóz. nélkül → bcrypt → argon2)
2. feladat – Igaz vagy hamis?
Értékeld az alábbi állításokat:
a) „A HTTPS megvédi az adatbázisban tárolt jelszavakat."
b) „A salt (só) titkos érték, amelyet külön kell tárolni a hash-től."
c) „Ha az MD5 hash-t sóval egészítem ki, az megfelel production jelszótárolásra."
d) „A bcrypt szándékosan lassú – ez a tervezési cél, nem hiba."
e) „A .env.example fájl biztonságosan kerülhet Git-be."
Válaszok:
a) Hamis – HTTPS az átvitelt védi, nem a tárolást
b) Hamis – a só nem titok, tárolható a hash mellé
c) Hamis – MD5 alapvetően nem megfelelő jelszóhash-hez, sóval sem
d) Igaz – a lassúság véd a brute force ellen
e) Igaz – ha nem tartalmaz valódi értékeket, csak kulcs-neveket
3. feladat – Kódhibák azonosítása
Az alábbi kódrészletben jelöld meg az összes security problémát, és magyarázd el, mit kellene másképp csinálni:
import hashlib
import logging
@app.route('/login', methods=['POST'])
def login():
email = request.form['email']
password = request.form['password']
# Jelszó ellenőrzése
password_hash = hashlib.md5(password.encode()).hexdigest()
user = User.query.filter_by(email=email, password=password_hash).first()
if user:
logging.info(f"Successful login: {email} / {password}")
return jsonify({"token": generate_token(user)})
else:
return jsonify({"error": f"No user found with email {email}"}), 401
Megoldás – azonosítandó hibák:
- MD5 jelszóhash – lecserélendő bcrypt-re
- Jelszó a logban – soha ne logolj jelszót (sem plaintext, sem hash)
- Email a hibaüzenetben – ne áruld el, melyik email létezik/nem létezik (user enumeration)
4. feladat – .gitignore ellenőrzés
Az alábbi fájlok közül melyek nem kerülhetnek soha Git-be?
| Fájl | Git-be kerülhet? |
|---|---|
.env | ? |
.env.example | ? |
config.py (hardcoded API kulcsokkal) | ? |
config.py (csak os.getenv() hívásokkal) | ? |
secrets/production.json | ? |
README.md (ahol le van írva a DB jelszó) | ? |
Válaszok:
.env→ NEM (valódi értékeket tartalmaz).env.example→ IGEN (csak kulcsneveket tartalmaz)config.pyhardcoded kulcsokkal → NEMconfig.pycsakos.getenv()hívásokkal → IGENsecrets/production.json→ NEMREADME.mdDB jelszóval → NEM
5. feladat – PII azonosítása
Az alábbi adatmezők közül melyek tekinthetők PII-nek?
- A) Felhasználó neve (
full_name) - B) Felhasználó e-mail címe
- C) Anonymizált felhasználói ID (UUID)
- D) Felhasználó születési dátuma
- E) Egy blogbejegyzés publikálási dátuma
- F) Felhasználó IP-címe
- G) Termék neve az adatbázisban
- H) Felhasználó telefonszáma
PII: A, B, D, F, H
Nem PII: C (ha nem kapcsolható személyhez), E, G
6. feladat – Jelszóhash működés megértése
Miért ad a bcrypt különböző hash-t ugyanarra a jelszóra, ha kétszer futtatod?
import bcrypt
password = "titkos123".encode()
hash1 = bcrypt.hashpw(password, bcrypt.gensalt())
hash2 = bcrypt.hashpw(password, bcrypt.gensalt())
print(hash1 == hash2) # Mit ír ki és miért?
Válasz: False – mert minden gensalt() hívás új, véletlenszerű sót generál. A különböző só különböző hash-t eredményez. Ez szándékos: ezért nem lehet rainbow table-lel támadni. A bcrypt.checkpw() a hash-ben tárolt sót használja az összehasonlításhoz, így az ellenőrzés akkor is működik, ha a hash különböző.
7. feladat – Szituációs döntés
Az alábbi szituációkban mit tegyél? Válaszd a helyes megközelítést és indokold meg.
Szituáció A: Véletlenül commitoltad a .env fájlt, de még nem pusholtad.
- 1. opció:
git commit --amenda.envnélkül, majd push - 2. opcio: Törli a
.env-t, új commitot csinál, push - 3. opcio: Visszavonja az API kulcsokat, majd git history tisztítása
Helyes: 3. opcio – a kulcsok visszavonása az első lépés, még ha nem is pusholtad, mert nem tudod, más látta-e. Utána history tisztítás.
Szituáció B: A senior fejlesztő azt mondja: „Egyelőre SHA-256-ot használunk jelszóra, majd ha lesz idő, átírjuk bcrypt-re."
- 1. opcio: Elfogadod, megcsinálod SHA-256-tal
- 2. opcio: Megkérdezed, mikor lesz ez az átírás, és kéred, hogy prioritást kapjon
- 3. opcio: Csendben bcrypt-et implementálsz SHA-256 helyett
Helyes: 2. opcio – jelzed a kockázatot, de nem döntöd felül a seniort egyeztetés nélkül. A security kockázatot kommunikálni kell, nem elhallgatni.
8. feladat – Hibakeresés: mi a probléma?
Egy felhasználó jelenti: bejelentkezés után néhány perc múlva automatikusan kijelentkezik, és nem érti, miért. A fejlesztő megnézi a logot:
[INFO] User login: user_id=42, email=user@example.com, session_token=eyJhbGc...
[INFO] Session validated: token=eyJhbGc...
[INFO] Session expired: token=eyJhbGc...
Kérdés 1: Mi a security probléma a logban?
Kérdés 2: Mi lehet a bejelentkezési probléma oka?
Válaszok:
- A session token a logban jelenik meg – ha valaki hozzáfér a logfájlhoz, ellophatja a tokent és megszemélyesítheti a felhasználót. Logba csak az ID kerüljön, nem a teljes token.
- A session lejárati ideje valószínűleg túl rövid, vagy a token érvényesítési logika hibás.
9. feladat – Összefüggések megértése
Párosítsd össze a fogalmakat és definíciókat:
| Fogalom | Definíció |
|---|---|
| Salt | A. Szándékosan lassú hash algoritmus jelszóhoz |
| bcrypt | B. Véletlenszerű érték, amelyet hash előtt a jelszóhoz adnak |
| Rainbow table | C. Előre kiszámolt hash → jelszó párok adatbázisa |
| PII | D. Személyes azonosításra alkalmas adat |
| .env.example | E. Konfigurációs kulcsokat dokumentáló, értékek nélküli sablon |
| HTTPS | F. Az adatátvitelt titkosítja átvitel közben |
Megoldás: Salt→B, bcrypt→A, Rainbow table→C, PII→D, .env.example→E, HTTPS→F
10. feladat – Gondolkodj fejlesztőként
Egy új featurét kapsz: a felhasználók exportálhatják az adataikat (GDPR-kompatibilis export). Az export tartalmazza: nevet, e-mail címet, összes posztot, regisztrációs dátumot.
Kérdés: Milyen security és privacy szempontokat kell figyelembe venni az implementációnál? Írj legalább 4 szempontot.
Lehetséges válaszok (minimum 4):
- Hitelesítés: Csak a saját adatait exportálhatja a felhasználó – ellenőrizni kell, hogy a kért export az aktuálisan bejelentkezett felhasználóhoz tartozik-e
- Rate limiting: Korlátozzuk, hányszor exportálhat egy felhasználó (hogy ne lehessen scraping eszközként használni)
- Nem kerülhet bele: jelszóhash, belső ID-k, más felhasználók adatai
- Letöltési link lejárata: Ha ideiglenes letöltési linket generálsz, legyen rövid lejáratú
- Logolás: Loggolj minden exportot (ki, mikor), de maga az export tartalma ne kerüljön logba
- HTTPS: Az export csak titkosított kapcsolaton töltődhet le