Veri Şifreleme

Starter kit veritabanındaki küçük bir hassas veri kümesini şifreler. Bu döküman hangi anahtarın bu veriyi koruduğunu, bu anahtarın APP_KEY ile ilişkisini ve adanmış anahtarı benimserken/rotasyon yaparken çalıştırılacak komutları anlatır.

Ne şifreleniyor, hangi anahtarla

Veri Nerede Kullanılan anahtar
Hassas ayar değerleri (mail.password, storage.spaces_secret, storage.aws_secret, storage.hetzner_secret, turnstile.secret_key, postman.api_key, apidog.access_token) settings.value DataCrypt (adanmış anahtar)
İki faktörlü doğrulama sırrı ve kurtarma kodları users.two_factor_secret, users.two_factor_recovery_codes DataCrypt (adanmış anahtar)
Session'lar, imzalı URL'ler, cookie'ler ve hâlâ Crypt facade'ını çağıran uygulama kodu — APP_KEY (değişmedi)

DataCrypt (Lvntr\StarterKit\Support\Encryption\DataCrypt), Laravel'in Crypt facade'ıyla aynı API'ye sahip bir facade'dır (encryptString(), decryptString(), encrypt(), decrypt()). Tek fark hangi anahtarın kullanıldığıdır.

APP_KEY vs DATA_ENCRYPTION_KEY

Bunlar bağımsız yaşam döngülerine sahip iki ayrı anahtardır:

  • APP_KEY — Laravel'in kendi anahtarı. Session'ları, cookie'leri, imzalı URL'leri ve Crypt facade'ını destekler. php artisan key:generate bu anahtarı yeniden üretir.
  • DATA_ENCRYPTION_KEY — yukarıda listelenen ayar ve 2FA verisi için adanmış bir anahtar. Kendi .env değişkenine ve kendi komutlarına (encryption:key, encryption:rekey, encryption:health) sahiptir. php artisan key:generate DATA_ENCRYPTION_KEY veya DATA_ENCRYPTION_PREVIOUS_KEYS'i asla okumaz veya yazmaz — yalnızca APP_KEY'e dokunur.

Bu ayrım şu nedenle var: key:generate çalıştığı anda her şifreli ayarı ve her kullanıcının 2FA sırrını sessizce bozuyordu (Crypt/Fortify'ın varsayılan encrypter'ı APP_KEY'e bağlı, SettingService de ortaya çıkan DecryptException'ı yutup null döndürüyor, dolayısıyla hata görünmüyordu bile). Adanmış anahtar bu bağımlılığı kaldırır.

Anahtar çözümleme sözleşmesi

Bu tablo bütün güvenlik özelliğidir. DataEncrypterFactory tarafından uygulanır ve DataCrypt tarafından zorlanır.

Durum Birincil anahtar (yazma) Previous-key zinciri (okuma)
DATA_ENCRYPTION_KEY boş (adanmış anahtarı henüz benimsememiş bir kurulum) APP_KEY DATA_ENCRYPTION_PREVIOUS_KEYS (varsa)
DATA_ENCRYPTION_KEY dolu DATA_ENCRYPTION_KEY DATA_ENCRYPTION_PREVIOUS_KEYS, ardından en sonda APP_KEY
  • APP_KEY birincil anahtar değilse her zaman okuma zincirinin sonuna eklenir — benimseme öncesi yazılmış her şey hiçbir komut çalıştırmadan okunabilir kalır.
  • Zincir sırayla denenir, tekrarlar temizlenir, boş elemanlar atılır.
  • Zincirdeki bozuk bir anahtar sessizce atlanmaz — RuntimeException fırlatılır. Sessiz atlama bir değeri rotasyon ortasında okunamaz gibi gösterip operatörü DATA_ENCRYPTION_PREVIOUS_KEYS'i boşaltmaya iter — bu ise kalıcı veri kaybıdır.

Hiçbir şey yapmamak geçerli bir tercihtir. encryption:key'i hiç çalıştırmayan bir uygulama bugünküyle bit-bit aynı çalışmaya devam eder: DATA_ENCRYPTION_KEY boş kalır, birincil anahtar APP_KEY olmaya devam eder, davranış değişmez. composer update/sk:update sonrası hiçbir şey benimsemeyi zorlamaz.

Üç komut

Komut Amaç Diske yazar mı?
php artisan encryption:key Yeni bir DATA_ENCRYPTION_KEY üretir ve yerini aldığı anahtarı DATA_ENCRYPTION_PREVIOUS_KEYS'te korur Evet — .env
php artisan encryption:rekey Her ayar/2FA satırını mevcut birincil anahtara yeniden şifreler Evet — veritabanı (.env değil)
php artisan encryption:health Her satırın hangi anahtara ihtiyacı olduğunu ve DATA_ENCRYPTION_PREVIOUS_KEYS'in temizlenmesinin güvenli olup olmadığını raporlar Hayır — salt okunur

encryption:key

php artisan encryption:key
  • Mevcut birincil anahtarı .env'den çözer (önbelleklenmiş config'ten değil), yapılandırılmış cipher için bellekte rastgele yeni bir anahtar üretir, DATA_ENCRYPTION_PREVIOUS_KEYS'i eski birincili başa ekleyerek yazar ve ancak ondan sonra yeni DATA_ENCRYPTION_KEY'i yazar. Bu sıra kasıtlıdır: iki yazma arasında bir çökme, eski anahtarı hâlâ birincil ve fazladan listelenmiş bırakır — asla tersi değil, tersi her şifreli satırı sahipsiz bırakırdı.
  • .env, uygulamanın önyüklendiği aynı ayrıştırıcıyla (phpdotenv) okunur, regex ile değil — ${VAR} ile enterpolasyonlu bir atama (örn. DATA_ENCRYPTION_KEY=${APP_KEY}), DATA_ENCRYPTION_PREVIOUS_KEYS'e yazılmadan önce gerçek anahtar materyaline çözülür, hiçbir zaman ${APP_KEY} referansı olarak değil. Ayrıştırıcının anlam veremediği bir .env, hiçbir anahtar üretilmeden veya hiçbir şey yazılmadan önce komutu durdurur — yarım uygulanmış bir rotasyon kalmaz — ve ayrıştırıcının kendi hata mesajı gösterilmez, çünkü bozuk satırı olduğu gibi alıntılayabilir ve bu satır anahtar materyali olabilir.
  • Yetki dosyada olmalı. Süreç ortamı bu anahtarlardan birini ayarlıyorsa — ya da .env içindeki enterpolasyonlu bir değerin işaret ettiği bir değişkeni ayarlıyorsa — çalışan uygulama dosyanın söylediği değeri değil, o değeri çözer. encryption:key bu ayrışmayı tespit edip hiçbir şey üretmeden durur; anahtarın adını söyler, iki değerden hiçbirini yazmaz. .env'i yeniden yazmak işe yaramaz: süreçteki değer kazanmaya devam eder ve rotasyon, uygulamanın hiç kullanmadığı bir anahtarı emekliye ayırıp gerçekten kullandığını listeden düşürür — mevcut şifreli veri okunamaz hale gelir. Süreç değişkenini kaldırın (ya da dosyayla aynı değere getirin) ve komutu tekrar çalıştırın.
  • APP_KEY bu komut tarafından hiçbir seçenekte okunmaz, değiştirilmez, yeniden yazılmaz.
  • --show yeni üretilen bir anahtarı stdout'a yazar, hiçbir şey diske yazmaz. .env'e dokunmadan bir anahtarın neye benzediğine bakmak için kullanın.
  • Production gibi görünen bir ortamda çalıştırmak için --force gerekir, çünkü anahtarı burada döndürmek encryption:rekey tamamlanana kadar her şifreli değeri okunamaz yapar. --force'u ancak bir veritabanı yedeğiniz ve bir bakım penceresi varken tekrar çalıştırın.
  • Yeni anahtar .env'e yazılır ama asla ekrana basılmaz. Yerini aldığı anahtar da asla basılmaz veya loglanmaz — çıktıda yalnızca geldiği değişkenin adı görünür.

encryption:rekey

php artisan encryption:rekey
php artisan encryption:rekey --dry-run
php artisan encryption:rekey --only=settings
php artisan encryption:rekey --only=two-factor
php artisan encryption:rekey --chunk=200
  • Birincil olmayan bir anahtarla çözülen her ayar ve 2FA satırını mevcut birincil anahtara yeniden şifreler. Yapılandırılmış hiçbir anahtarın okuyamadığı bir satır dokunulmadan bırakılır ve raporlanır — asla silinmez veya boşaltılmaz.
  • --dry-run her çözme denemesini yapar ve aynı özeti tek bir bayt yazmadan basar.
  • --only=settings veya --only=two-factor (birleştirmek için virgülle) çalıştırmayı tek bir yüzeyle sınırlar.
  • --chunk=<n> (varsayılan 200, maksimum 2000) her gidiş-dönüşte kaç satırın okunup kilitlenip yeniden yazılacağını kontrol eder.
  • Bu komut bir bakım penceresine aittir. Her chunk'ı bir transaction altında yeniden okur ve kilitler, böylece eşzamanlı bir yazma bayat bir yeniden yazmayla ezilmez — ama büyük bir rekey'in yoğun trafikli bir production veritabanında planlı bir pencere dışında çalıştırılmaması gerekir.
  • Rekey hiçbir updated_at'i güncellemez — bu bir depolama formatı değişikliğidir, iş kuralı değişikliği değil.

encryption:health

php artisan encryption:health
php artisan encryption:health --json

Salt okunur — kilit almaz, transaction açmaz, canlı bir veritabanına karşı çalıştırmak güvenlidir.

Sonuçlar (exit kodu makine tarafından okunabilir yarısıdır):

Sonuç Exit kodu Anlamı
safe-to-clear 0 Taranan her değer yalnızca birincil anahtarla okunuyor; her yüzey tamamen tarandı. DATA_ENCRYPTION_PREVIOUS_KEYS temizlenebilir.
rekey-required 1 En az bir değer birincil olmayan bir anahtara ihtiyaç duyuyor. Henüz hiçbir şey kaybolmadı, ama previous-key listesini şimdi temizlemek kaybettirir. encryption:rekey çalıştırın.
not-covered 1 Bir yüzey, bu kitin kurmadığı bir encrypter tarafından servis ediliyor ya da starter-kit.encryption config bloğu yok olduğu için yapılandırılan anahtar etkisiz. Hiçbir şey kaybolmadı, ama aşağıdaki atıf kurulumun gerçek okuma/yazma yolunu tarif etmiyor; bu yüzden buradan "safe to clear" iddia edilemez. Bkz. aşağıdaki Yüzey kapsamı.
incomplete 1 Bir yüzey tam olarak taranamadı, dolayısıyla "safe" iddia edilemez — çözdüğü anahtar zinciri artık .env/süreç ortamıyla eşleşmeyen önbelleklenmiş bir config de buna dahildir. php artisan config:clear çalıştırın (production'da config önbelleği kullanıyorsanız ardından yeniden önbelleğe alın) ve tekrar çalıştırın.
unreadable 2 Yapılandırılmış hiçbir anahtarın okuyamadığı bir değer var. Onu yazan anahtar .env'den eksik ve geri eklenmeli — asla temizlenmemeli.
key-error 2 Anahtar zincirinin kendisi çözülmüyor; hiçbir şey atfedilemedi.

Sonuç yalnızca aşağı düşer, asla yükselmez — yanlış bir "safe to clear" bu komutun veri kaybına yol açabilecek tek çıktısıdır.

Yüzey kapsamı

Her iki komut da yalnızca kitin kendi anahtar zincirini değil, her yüzeyi gerçekte hangi encrypter'ın servis ettiğini raporlar. Bu önemlidir, çünkü 2FA yüzeyi zorunlu olarak kit üzerinden okunmaz: Fortify Fortify::$encrypter ?? Model::$encrypter ?? Crypt sırasını çözer ve kit, tüketicinin kendi ayarladığı bir encrypter'ı bilinçli olarak ezmez (StarterKitServiceProvider::configureDataEncryption()).

  • encryption:health her yüzeyin arkasındaki encrypter'ı isimlendirir. Kitin kurmadığı bir encrypter tarafından servis edilen yüzey unvouched (kefil olunamayan) olarak raporlanır ve sonuç not-covered'a (exit 1) düşer — satır atfı saklanan baytlar hakkında hâlâ doğrudur, ama bu kurulumun okuyup yazdığı yol hakkında hiçbir şey söylemez.
  • Eski yayınlanmış config boşluğu ayrı bir teşhis olarak raporlanır. Encryption bloğundan önce yayınlanmış bir config/starter-kit.php, starter-kit.encryption değerini null yapar; böylece DATA_ENCRYPTION_KEY etkisiz kalır ve birincil anahtar sessizce APP_KEY yedeğine düşer — bu durum önceden "safe to clear" olarak okunabiliyordu. Artık not-covered verir; config'i yeniden yayınlayın (php artisan vendor:publish --tag=starter-kit-config --force) ve tekrar çalıştırın.
  • Seçilen bir yüzey unvouched ise encryption:rekey tek bir satır bile okumadan reddeder ve hem yüzeyi hem de onu dışarıda bırakan --only= bayrağını isimlendirir. Sessizce daraltılıp ardından eksiksiz bir rekey gibi raporlanan bir çalıştırma, hatadan daha kötüdür: satırları, okuyan encrypter'ın elinde olmayan bir anahtara yeniden yazar ve her 2FA girişini başarısız bir challenge'a çevirir. Seçilen yüzeylerin tamamı kapsam içindeyse çalıştırma bundan etkilenmez.

Mevcut bir kurulumda adanmış anahtarı benimsemek

encryption:key'i hiç çalıştırmamış bir kurulumun bunu yapması zorunlu değildir. Adanmış anahtarı benimsemeyi seçerseniz şu dört adımı sırayla çalıştırın:

php artisan encryption:key
php artisan config:clear   # config önbelleklenmişse rekey HÂLÂ eski birincil anahtarı çözer
php artisan encryption:rekey
php artisan encryption:health

config:clear iki komutun arasında durur, sonrasında değil. encryption:key .env'i yazar ama önbelleklenmiş config önceki zinciri sunmaya devam eder — dolayısıyla temizlemeden önce çalıştırılan bir rekey, her satırı az önce emekliye ayrılan anahtara yeniden şifreler ya da yapacak hiçbir şey bulamaz. Önce temizlemek, encryption:rekey'in veriyi taşıması gereken anahtarı görmesini sağlayan adımdır.

Ardından, yalnızca encryption:health safe-to-clear raporladıktan sonra:

# .env'i elle düzenleyin ve değeri temizleyin:
DATA_ENCRYPTION_PREVIOUS_KEYS=
php artisan config:clear
php artisan encryption:health

Production'da config önbelleği kullanıyorsanız yukarıdaki her config:clear'dan hemen sonra php artisan config:cache'i tekrar çalıştırın.

Düzenlemeden sonraki ikinci encryption:health çalıştırması da safe-to-clear raporlamadan DATA_ENCRYPTION_PREVIOUS_KEYS'i temizlemeyin. Başka bir şey raporlarsa eski değeri geri koyun ve araştırın — her satır birincil anahtarda doğrulanmadan previous-key listesini temizlemek, bir rotasyonu kalıcı veri kaybına çevirir.

Zaten benimsenmiş bir anahtarı rotasyona sokmak

Rotasyon aynı üç komutu, aynı sırayla, aynı bakım penceresi gerekliliğiyle kullanır:

php artisan encryption:key --force   # --force yalnızca production gibi bir ortamda gerekli
php artisan config:clear             # config önbelleklenmişse rekey HÂLÂ eski birincil anahtarı çözer
php artisan encryption:rekey
php artisan encryption:health

Ardından DATA_ENCRYPTION_PREVIOUS_KEYS'i elle temizleyin, config:clear çalıştırın ve yukarıdaki benimseme akışındaki gibi tekrar encryption:health çalıştırıp doğrulayın. encryption:rekey'i bir bakım penceresi içinde çalıştırın — komut eşzamanlı okuma/yazmaya karşı güvenli yazılmış olsa da büyük bir tabloyu aktif trafik ortasında rekey'lemek doğru değildir.

Bu özelliğin geri alınması (rollback)

Şifreleme kodunun kendisi geri alınırsa (git revert), DataCrypt var olmaktan çıkar ve her çağrı noktası APP_KEY'e bağlı olan Crypt'e düşer. Adanmış anahtarla yazılmış her satır, revert anında okunamaz hale gelir — önce şunu yapmadıkça:

  1. .env'de DATA_ENCRYPTION_KEY'i APP_KEY ile tam olarak aynı değere ayarlayın.
  2. Her satırın o ortak değere yeniden şifrelenmesi için php artisan encryption:rekey çalıştırın.
  3. Kod revert'i ancak ondan sonra uygulayın.

Rotasyon yalnızca kısmen uygulandıysa (örn. encryption:key çalıştı ama encryption:rekey bitmedi), DATA_ENCRYPTION_KEY'i önceki değerine geri döndürün ve DATA_ENCRYPTION_PREVIOUS_KEYS'e dokunmayın — zincir sırayla denendiği için veri okunabilir kalır. encryption:health ile doğrulayın.

Ayrıca bakınız

  • docs/server-migration-runbook.md — kurulu bir uygulamayı şifreli veri kaybetmeden yeni bir sunucuya taşımak için kopyala-yapıştır kontrol listesi.
  • docs/artisan-commands.md — tam komut referansı.