Özet
| Değişiklik tarihi | 18 Mayıs 2026 |
| Değişen dosya | html/js/calendar/crypto-worker.js |
| Etkilenen fonksiyon | derivePasskeyKEK() |
| Algoritma | HKDF-SHA-256 → AES-GCM-256 (değişmedi) |
| Neden | Safari / WebKit içindeki bilinen deriveKey() Worker hang yolundan kaçınmak |
| Kriptografik etki | Yok. İki yol da aynı anahtar materyalini üretir. |
| Doğrulama | Parite testi tests/crypto/hkdf-equivalence.mjs aynı decrypt davranışını doğrular |
Arka plan: passkey KEK mimarisi
PayCal, kullanıcı başına verileri şifrelenmiş olarak saklanan ve hiçbir zaman düz metin olmayan bir data encryption key (DEK) ile korur. Login sırasında bunu çözmek için PayCal, kullanıcının passkey credential değerinden bir key encryption key (KEK) türetir.
Türetim, kriptografik işlemleri ana tarayıcı thread’inden izole eden sandboxed bir arka plan thread’i olan Web Worker içinde çalışır. Worker, HKDF key derivation adımını yapmak için tarayıcının WebCrypto API’sini (crypto.subtle) çağırır.
Türetimin girdisi, WebAuthn passkey credential içindeki kararlı credentialId değeridir; imza değildir. ECDSA imzaları deterministik değildir, bu yüzden imzadan türetilen bir KEK her login işleminde değişir ve DEK unwrap başarısız olur. credentialId kullanımı, kullanıcı aynı cihazla her authenticate olduğunda aynı KEK’in üretilmesini garanti eder.
HKDF parametreleri şunlardır:
- Algoritma: HKDF-SHA-256
- IKM:
credentialIdiçin UTF-8 encoding - Salt: Kullanıcı başına server-side saklanan rastgele 32-byte değer
- Info: Sabit domain string
paycal-passkey-kek - Çıktı: 256-bit AES-GCM anahtarı (non-extractable)
WebCrypto API: aynı anahtara iki yol
WebCrypto API, HKDF girdisinden türetilmiş anahtar üretmek için iki yol sunar:
crypto.subtle.deriveKey(algorithm, baseKey, derivedKeyType, extractable, usages)- HKDF türetimini yapar ve doğrudan bir
CryptoKeynesnesi döndürür. Daha kompakt çağrıdır: çağıran taraf AES-GCM-256 gibi çıktı anahtarı algoritmasını tek adımda belirtir. crypto.subtle.deriveBits(algorithm, baseKey, length)+crypto.subtle.importKey(format, keyData, algorithm, extractable, usages)- HKDF türetimini yapar ve önce raw bytes döndürür. Çağıran taraf sonra bu byte’ları ikinci bir
importKeyçağrısıylaCryptoKeyolarak import eder. Bu iki adımlı eşdeğerdir: oluşan anahtar nesnesi tür, uzunluk, extractability ve izinli usages bakımından aynıdır.
İki yol da aynı HKDF-SHA-256 fonksiyonunu çağırır, aynı IKM, salt ve info parametrelerini kullanır ve aynı 256 bit anahtar materyalini üretir. Tek fark, Web Worker runtime’ın çağrıyı dahili olarak nasıl işlediğidir.
Sorun: Safari / WebKit Web Workers içinde gözlenen deriveKey() hang
Safari / WebKit içinde çağrı bir Web Worker’dan yapıldığında operasyonel olarak gözlenen bir deriveKey() hang yolu vardır. Safari kullanıcıları, Worker türetimi tamamlasın diye beklerken hata, timeout veya görünür geri bildirim olmadan duran bir login akışı yaşayabiliyordu.
Aynı çıktıyı üretmek için kullanılan deriveBits() çağrısı bu hang davranışını göstermez. Sorun Worker execution context içindeki deriveKey() code path’e özgüdür; alttaki HKDF algoritmasını etkilemez.
deriveKey() ve deriveBits(), Apple tarafından belgelenen ve W3C tarafından belirtilen birinci sınıf WebCrypto API’leridir. Bu use case için semantikleri aynıdır. Hang bir runtime regression’dır, iki çağrı arasında tasarım farkı değildir.
Bu exact Worker + HKDF + deriveKey() kombinasyonu için public indexed canonical Apple bug ticket yoktur. Bu durum, operasyonel olarak keşfedilen, production’da workaround edilen ve public ticket ile resmi olarak izlenmeyen Safari/WebKit edge-case crypto regression sınıfıyla tutarlıdır. Engineering kararımız, WebKit sürümünden bağımsız olarak problemli çağrı yolundan tamamen kaçınmak ve runtime’da browser state tespiti yapmamaktır.
Ne değiştirdik
html/js/calendar/crypto-worker.js içindeki derivePasskeyKEK() fonksiyonu, tek türetim yolu olarak deriveBits() + importKey() kullanacak şekilde güncellendi. deriveKey() çağrısı tamamen kaldırıldı.
Yeni türetim sırası:
- IKM byte’larını HKDF base key olarak
importKey('raw', ikmMaterial, 'HKDF', false, ['deriveBits'])ile import et deriveBits(hkdfParams, ikm, 256)ile 256 raw bit türet- Bu bitleri AES-GCM-256 anahtarı olarak
importKey('raw', keyBits, { name: 'AES-GCM', length: 256 }, false, ['encrypt', 'decrypt'])ile import et
HKDF parametreleri (SHA-256 hash, kullanıcı başına salt, sabit info string paycal-passkey-kek) değişmedi. Çıktı anahtar türü, uzunluğu ve non-extractable flag değişmedi. Eski code path’i daha önce kullanan kullanıcılar için stored wrapped DEKs tamamen uyumludur: re-wrapping veya re-enrollment gerekmez.
Kriptografik eşdeğerlik kanıtı
Eşdeğerliği yalnızca akıl yürütmeyle iddia etmiyoruz. tests/crypto/hkdf-equivalence.mjs dosyası şu parite testini içerir:
- Aynı HKDF parametreleriyle Yol A (
deriveKey()) üzerinden anahtar türetir - Aynı HKDF parametreleriyle Yol B (
deriveBits()+importKey()) üzerinden anahtar türetir - Yol A anahtarıyla test payload’ını encrypt eder
- Bu ciphertext’i Yol B anahtarıyla decrypt eder
- Decrypted bytes değerlerinin original plaintext ile birebir aynı olduğunu doğrular
- Tersini tekrarlar: Yol B ile encrypt, Yol A ile decrypt
İki decrypt işlemi de başarılı olur ve aynı byte’ları üretirse anahtarlar operasyonel olarak aynıdır. Test Node.js 18+ üzerinde geçer ve SOC 2 false-positive adjudication ledger içinde CRYPTO-001 evidence item olarak referanslanır.
Ne değişmedi
- Anahtar türetme algoritması hâlâ HKDF-SHA-256
- Çıktı anahtarı hâlâ 256-bit AES-GCM
- Çıktı anahtarı Worker context içinden hâlâ non-extractable
- IKM kaynağı hâlâ kararlı
credentialId(ECDSA imzası değil) - HKDF info string hâlâ
paycal-passkey-kek - Kullanıcı başına salts değişmedi
- Kullanıcı aksiyonu, re-enrollment veya credential rotation gerekmez
- Eski KEK altında wrapped data encryption key (DEK), yeni KEK ile tamamen uyumludur: byte’lar aynı anahtardır
Bunu neden yayınlıyoruz
Bu değişiklik güvenliği etkilemez. Ancak kodumuzun belirli bir browser engine üzerindeki davranışını etkiler ve client-side cryptography stack’imizin en güvenlik hassas kısmına dokunur.
Bunu yayınlıyoruz çünkü transparency policy’miz, key derivation logic içindeki her değişikliğin, davranış olarak nötr olsa bile, teknik gerekçe ve verification evidence ile birlikte kullanıcılar ve auditorlar tarafından incelenebilir şekilde public olarak belgelenmesini gerektirir.
Kriptografik değişiklikler, kasıtlı olarak inert olsalar bile asla görünmez olmamalıdır.
Referanslar
-
WebKit Blog — Update on Web Cryptography
webkit.org/blog/7790/update-on-web-cryptography/
Official Apple/WebKit announcement adding HKDF and related WebCrypto improvements. Authoritative baseline for HKDF support in WebKit. -
Apple Developer — SubtleCrypto Documentation
developer.apple.com/documentation/webkitjs/subtlecrypto
Confirms bothderiveBits()andderiveKey()are first-class APIs in WebKit. Relevant to the architectural justification for usingderiveBits(). -
MDN — SubtleCrypto.deriveBits()
developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits
W3C-aligned specification reference forderiveBits(), including Worker support. Confirms this call is a valid and fully supported path in Worker contexts. -
MDN — SubtleCrypto.deriveKey()
developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey
W3C-aligned specification reference forderiveKey(). Comparing this with thederiveBits()spec confirms both are specified to work in Workers; the observed Safari hang is a runtime regression, not a spec divergence. -
Stack Overflow — HKDF output length and
deriveKeyvsderiveBitssemantics
stackoverflow.com/questions/79365928/how-to-specify-desired-hkdf-output-length-with-crypto-subtle-derivekey
Practical community discussion showing why developers intentionally preferderiveBits()for deterministic control and cross-browser compatibility when deriving HKDF keys.