تصور کنید برنامهنویسی باشید که میخواهد امنیت سایتش را با پاسکیها بالا ببرد، اما در برابر پیچیدگیهای دیتابیس متوقف شده است. اگر هنوز از روشهای سنتی ذخیرهسازی مدارک احرازی استفاده میکنید، باید بدانید که یک استاندارد جدید در راه است تا این کابوس فنی را به سادگیِ مدیریت هشِ پسوردها تبدیل کند. در واقع میتوان شباهتی میان امنیت حافظه (Memory Safety) و فیشینگ در نظر گرفت: همانطور که ایمنی حافظه تنها راهکار اصولی برای مقابله با فساد حافظه است، پاسکیها نیز تنها دفاع اصولی در برابر موج فعلی حملات فیشینگ هستند.
با وجود این اهمیت، پیادهسازی پاسکیها در سمت سرور همچنان یک مانع بزرگ برای توسعهدهندگان است. برای رفع این مشکل، فیلیپ والترز (Filippo)، یکی از توسعهدهندگان اصلی زبان Go، در ۲۰ ژوئیه ۲۰۲۶، روشی را برای انتزاع این پیچیدگیها معرفی کرد. هدف او این است که رکوردهای پاسکی (Passkey) به جای جداول پیچیده چندستونی، به صورت رشتههای مبهم (Opaque Strings) ذخیره شوند؛ دقیقاً مشابه روشی که توسعهدهندگان در حال حاضر برای مدیریت هشهای رمز عبور استفاده میکنند.
پیادهسازی WebAuthn (استاندارد احراز هویت وب) اغلب دشوارتر از هش کردن پسوردهای قدیمی است؛ چراکه برای حفظ مقاومت در برابر فیشینگ، نیازمند ادغام تنگاتنگ با مرورگر است. اگرچه مشخصات WebAuthn اجزای ضروری مانند نوع (type)، شناسه (id)، کلید عمومی (publicKey)، وضعیت پشتیبان (backupState)، روشهای انتقال (transports) و سایر پرچمها را تعریف کرده است، اما هیچ فرمت خاصی را برای پایگاهداده اجباری نکرده است. همین موضوع باعث ایجاد یک اکوسیستم پراکنده شده است که در آن کتابخانههای مختلف، طرحهای (Schema) متفاوتی را توصیه میکنند و این امر جابجایی بین بکاندهای مختلف بدون مهاجرت کامل دادهها را تقریباً غیرممکن میکند.
گسستگی در طرحهای پایگاه-داده و زمینه تاریخی
در حال حاضر هیچ اجماعی بر سر نحوه ذخیره رکوردهای اعتبارسنجی وجود ندارد. برای مثال، گوگل توصیه میکند از یک جدول پایگاهداده استفاده شود که در آن Credential ID به عنوان کلید اصلی (Primary Key) قرار گیرد و ستونهای مجزایی برای public_key ،backed_up و transports داشته باشد. از سوی دیگر، آدام لنگلی در راهنمای «Tour of WebAuthn» ساختاری مشابه را پیشنهاد میدهد که در آن یک کلید اصلی cred_id همراه با ستونهای مجزای public_key_spki و backed_up تعریف شده است.
در حالی که اکثر راهنماها توصیه میکنند برای مدیریت عملیات احراز هویت از کتابخانهها استفاده شود، اما اپلیکیشن همچنان با یک طرح دیتابیس غیرقابل تبادل (Non-interoperable) دست و پنجه نرم میکند. سپردن کامل جریان احراز هویت و تعاملات دیتابیس به یک کتابخانه شخص ثالث، بهویژه در محیطهای تولیدی (Production) پیچیده، اغلب غیرعملی است. این شکاف فنی، نیاز مبرمی به یک لایه انتزاعی میانرده ایجاد میکند تا توسعهدهنده را از جزئیات پیادهسازی هر کتابخانه خاص جدا کند.
منطق رکوردهای مبهم و استاندارد پیشنهادی
برای حل این مشکل، یک پیشنهاد جدید در وبسایت c2sp.org/passkey-record ارائه شده که از فرمتی وام گرفته شده از رشتههای PHC (Password Hashing Competition) استفاده میکند. در این مدل، به جای استفاده از یک جدول با چندین ستون، اپلیکیشن تنها یک رشته واحد را ذخیره میکند؛ چیزی شبیه به: $webauthn$v=1$transports=hybrid+internal$<base64 authenticator data>.
این رویکرد از رمزگذاری دادههای احراز هویت (CTAP2 CBOR) بهره میبرد که پیشتر در استاندارد WebAuthn تعریف شده است. این رمزگذاری در حال حاضر در قالب JSON در AuthenticatorAttestationResponse وجود دارد (که نوع بازگشتی تابع navigator.credentials.create() است)، حتی زمانی که از attestation استفاده نشود. در این فرمت رشتهای، تنها فیلد گمشده، «روشهای انتقال» (transports) است که به صورت پارامترهای PHC ذخیره میشود.
با تبدیل رکورد به یک بلوک مبهم (Opaque Blob)، اپلیکیشن تنها نیاز دارد ردیابی کند که کدام رکورد متعلق به کدام کاربر است. این دقیقاً همان منطق آشنای احراز هویت با پسورد است، با این تفاوت که یک حساب کاربری میتواند چندین پاسکی داشته باشد. با وجود یک فرمت ذخیرهسازی قابل تبادل و دقیق، توسعهدهندگان میتوانند کتابخانههای پاسکی یا حتی زبانهای برنامهنویسی بکاند خود را تغییر دهند، در حالی که دیتابیس اعتبارات آنها بدون تغییر باقی میماند.
پیادهسازی فنی و API زبان Go
والترز بر اساس این منطق، یک API بدون وضعیت (Stateless) برای بسته Go در پیشنویس احتمالی نسخه ۱.۲۸ طراحی کرده است. این جریان تعامل سرور و مرورگر را به توالیهای مشخصی تبدیل میکند:
جریان ثبتنام (Registration Flow):
- فراخوانی
RelyingParty.NewRegistrationبا جزئیات شناسایی شده کاربر و رکوردهای موجود پاسکی. - ارسال JSON بازگشتی به تابع
parseCreationOptionsFromJSON()و سپس ارسال آن بهnavigator.credentials.create()در مرورگر. - ارسال
PublicKeyCredentialکدگذاری شده با JSON بهRelyingParty.Register. - ذخیره رکورد پاسکی بازگشتی در پایگاهداده.
- فراخوانی
جریان ورود (Login Flow):
- فراخوانی
RelyingParty.NewLoginهمزمان با تولید صفحه ورود. - ذخیره درخواست بازگشتی در یک حافظه موقت (Cache) کلید-مقدار با زمان انقضای کوتاه (TTL) تحت
RequestID(request). - ارسال JSON به
parseRequestOptionsFromJSON()و سپس بهnavigator.credentials.get()در مرورگر. - ارسال
PublicKeyCredentialبازگشتی به تابعInspect. - استفاده از
requestIDبازگشتی برای بازیابی درخواست از کش و استفاده ازuserIDبرای بازیابی رکوردهای پاسکی از دیتابیس. - ارسال
PublicKeyCredential(به صورت JSON)، درخواست و رکوردهای پاسکی بهRelyingParty.Login.
- فراخوانی
این API برای «اعتبارهای قابلکشف» (Discoverable Credentials) — جایی که دستگاه احراز هویت، شناسهی کاربر را ذخیره کرده و به سرور ارائه میدهد — بهینه شده است. با این حال، از طریق متد RelyingParty.NewLoginForUser از جریانهای دوممرحلهای (2FA) یا درخواستهای احراز هویت مجدد نیز پشتیبانی میکند. همچنین این API به گونهای طراحی شده که با هر دو حالت رابط کاربری مودال (Modal) و شرطی (Conditional UI/Autofill) کار کند. علاوه بر این، توابع کمکی برای استخراج AAGUID و وضعیت BackedUp از رکورد، و همچنین ResponseBackedUp از JSON اثر انگشت دیجیتال تعبیه شده است.
پایان حملات برخورد کلید اصلی
استاندارد WebAuthn توصیه میکند که سرورها «باید» (SHOULD) تضمین کنند که حسابهای مختلف، شناسه اعتبار (Credential ID) یکسانی نداشته باشند. هدف این است که از حملاتی جلوگیری شود که در آن مهاجم با تزریق یک شناسه متصادم (Colliding ID) از طریق حساب خود، سرور را فریب دهد تا در هنگام جستوجو، از کلید عمومی یا شناسهی کاربر اشتباه استفاده کند.
اما والترز استدلال میکند که خودِ این «اندکس» (Index) دقیقاً همان چیزی است که آسیبپذیری را ایجاد میکند. او پیشنهاد میدهد با حذف ایندکس بر اساس شناسه اعتبار و جایگزینی آن با جستوجو بر اساس شناسهی کاربر (User ID) در مرحله اول، این حمله عملاً غیرممکن شود. اگر شناسهی کاربر مکانیسم اصلی جستوجو باشد، دیگر اهمیتی ندارد که دو کاربر مختلف به طور تصادفی شناسههای اعتبار متصادمی داشته باشند؛ زیرا همان منطقی که باعث میشود پسوردهای مشترک برای حسابهای مختلف بیاثر باشد، اینجا نیز صادق است. به قول نویسنده: «اجازه ندهید مهاجم کلید اصلی (PRIMARY KEY) شما را دیکته کند تا حملات برخورد کلید را تجربه نکنید».
مدیریت وضعیتهای پویا و متادیتا
اگرچه رکورد پاسکی در این مدل تغییرناپذیر (Immutable) است، اما برخی دادهها باید خارج از آن باقی بمانند. برای ارائه یک رابط کاربری با کیفیت، اپلیکیشنها ممکن است بخواهند متادیتایی مانند نامهای مستعار انتخاب شده توسط کاربر یا برچسبهای زمانی ایجاد و آخرین استفاده را ذخیره کنند. این دادهها نیازی به پردازش خاص توسط کتابخانه WebAuthn ندارند و میتوانند در جداول معمولی ذخیره شوند.
یک استثنای حیاتی، پرچم «پشتیبانگیری شده» (Backed up) است. پاسکیها گزارش میدهند که آیا با حسابهای ابری (مانند گوگل یا iCloud Keychain) همگام شدهاند یا خیر. از آنجایی که این وضعیت ممکن است در طول زمان یا در ورودهای مختلف تغییر کند، باید به صورت مجزا ذخیره شده و در هر بار ورود بهروزرسانی شود. برخی سرورها از این اطلاعات برای پیشنهاد حذف پسورد از حساب کاربر استفاده میکنند، هرچند والترز اشاره میکند که این منطق احتمالاً برای وبسایتهای معمولی که همچنان امکان بازنشانی رمز عبور از طریق ایمیل را دارند، بیش از حد تخمین زده شده و اهمیت کمی دارد.
این تغییر رویکرد به سمت ذخیرهسازی قابل تبادل، یکی از نقاط اصلی وابستگی به فروشنده (Vendor Lock-in) را از بین میبرد. برای بررسی بیشتر این پیشنهادات، میتوانید توسعه API زبان Go را از طریق پروفایلهای فیلیپ در Bluesky (@filippo.abyssdomain.expert) یا Mastodon (@[email protected]) دنبال کنید.
گام بعدی شما
- اگر از زبان Go استفاده میکنید، پیشنویس API والترز را برای آمادهسازی زیرساختهای نسخه ۱.۲۸ دنبال کنید.
- در طراحی دیتابیسهای جدید، به جای ستونهای متعدد برای WebAuthn، مدل ذخیرهسازی رشتههای مبهم را بررسی کنید.
- استراتژی بازیابی حساب خود را بازنگری کنید تا وابستگی به کلیدهای اصلی (Primary Keys) کاهش یابد.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو