اثر انگشت Canvas، WebGL و صدا: سختافزار چطور شما را لو میدهد
تنوع سه اثر انگشت رایج مرتبط با سختافزار از کجا میآید، چرا همان دستگاه در مرورگر دیگری متفاوت به نظر میرسد، و چرا افزونههای تزریق نویز معمولاً شما را بیشتر به چشم میآورند، نه کمتر. با اشارهای به WebGPU.
6 دقیقه مطالعه · انتشار ۱۹ شهریور ۱۴۰۵
از مرورگر بخواهید یک خط متن بکشد، یک مثلث رندر کند و تکهای صدا را پردازش کند که هیچکس نخواهد شنید. نتایج را هش کنید. هیچکدام به درخواست مجوز نیاز ندارد، کاربر چیزی متوجه نمیشود، و اینها با هم دستگاه شما را در میان میلیونها دستگاه در یک دستهٔ بسیار کوچک قرار میدهند. سازوکارش مرموز نیست و ارزش فهمیدن دارد.
Canvas: تفاوتهایی که میتوان کشید
اسکریپتهای اثر انگشت canvas همه شبیه هم هستند. یک <canvas> خارج از صفحه میسازند، جملهای را با یکی دو فونت مینویسند (معمولاً چیزی با گلیفهای متنوع مثل «Cwm fjordbank glyphs vext quiz»، بهعلاوهٔ یک emoji برای اطمینان)، چند مستطیل و کمان رنگی نیمهشفاف روی آن میگذارند، سپس toDataURL() را صدا میزنند تا PNG را بهصورت base64 بگیرند یا با getImageData() پیکسلهای خام را میخوانند، و نتیجه را هش میکنند.
دستورهای رسم یکسان باید پیکسلهای یکسانی تولید کنند. اما نمیکنند، چون مسیر از دستور تا پیکسل طولانی است:
- رسترسازی فونت. همان فایل فونت در Windows از DirectWrite، در macOS از Core Text و در Linux از FreeType عبور میکند، و هر کدام منحنیها را به شیوهٔ خودشان به پیکسل تبدیل میکنند.
- Anti-aliasing و رندر زیرپیکسلی. پیکسلهای نیمهشفاف لبهها به تنظیمات سیستم، جهت صفحهنمایش و روشن بودن رندر به سبک ClearType بستگی دارند.
- GPU و درایور. مرورگرها رسم canvas را به GPU میسپارند، و درونیابی کمانها و گرد کردن در حالتهای ترکیب رنگ بین کارتها و نسخههای درایور تفاوتهای ظریفی دارد.
- مدیریت رنگ. در برخی تنظیمات، پروفایل رنگ نمایشگر میتواند به خروجی canvas نشت کند.
هر تفاوت تکپیکسلی ناچیز است، اما هش همهٔ آنها را به رشتهای کاملاً متفاوت تقویت میکند. خبر خوب برای ردیاب، پایداری است: همان دستگاه و مرورگر امروز و ماه بعد دقیقاً همان تصویر را میکشد. همین پایداری است که آن را بهعنوان شناسه قابل استفاده میکند.
WebGL: پیکسلها بهعلاوهٔ انبوهی پارامتر
اثر انگشت WebGL دو لایه دارد. اولی شبیه canvas است: صحنهای با گرادیان و یک vertex shader رندر میشود، پیکسلها خوانده و هش میشوند. کامپایلر shader و دقت ممیز شناور GPU منبع تنوع دیگری اضافه میکنند.
لایهٔ دوم صریحتر است. WebGL بهسادگی انبوهی از واقعیتهای ثابت را گزارش میدهد. افزونهٔ WEBGL_debug_renderer_info رشتههای سازنده و رندرکنندهٔ واقعی را نشان میدهد، چیزی مثل ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version) یا ANGLE (NVIDIA, NVIDIA GeForce RTX 4070 Direct3D11 vs_5_0 ps_5_0, D3D11). بعد فهرست افزونههای پشتیبانیشده، محدودیتهای مختلف (بیشینهٔ اندازهٔ texture، ویژگیهای vertex، نمونههای anti-aliasing) و قالبهای دقت shader هست. آنتروپی ترکیبی بسیار بالاست؛ رشتهٔ رندرکننده بهتنهایی نام تراشه، backend گرافیکی و تقریباً نسل درایور را میگوید.
سازندگان مرورگر این را میدانند. Safari و Firefox در برخی پیکربندیها رندرکننده را به چیزی کلی مثل «Apple GPU» تقلیل میدهند.
صدا: اثر انگشتی که نمیشنوید
اثر انگشت AudioContext همانی است که بیش از همه به اشتباه «به میکروفون نیاز دارد» تصور میشود. نیاز ندارد. اسکریپت با OfflineAudioContext سیگنالی را در حافظه میسازد: یک نوسانساز موج مثلثی یا سینوسی تولید میکند، از یک DynamicsCompressorNode عبور میکند، چند هزار نمونه بهصورت آفلاین رندر میشود و خروجی جمع یا هش میشود. بیصدا، در چند ده میلیثانیه.
تنوع از کجا میآید؟ جزئیات پیادهسازی ممیز شناور، کتابخانهٔ DSP مرورگر، الگوریتم دقیق compressor و گرد کردن در پشتهٔ صوتی سیستمعامل. با کد یکسان، Chrome و Firefox در چند رقم اعشار با هم اختلاف دارند. همین کافی است.
چرا همان دستگاه با تغییر مرورگر عوض میشود
چون هر «مایل آخرِ» بالا را خود مرورگر پیاده میکند. Chrome دوبعدی را با Skia میکشد و WebGL را از طریق ANGLE به Direct3D 11 در Windows یا Metal در macOS میرساند. Firefox پشتهٔ خودش را دارد؛ Safari هم پشتهٔ دیگری. موتورهای شکلدهی متن و backendهای صوتی هم فرق دارند. بنابراین اثر انگشت canvas، WebGL و صدا در واقع ترکیب «این سختافزار + این سیستمعامل + این نسخهٔ مرورگر» را شناسایی میکند، نه سختافزار را بهتنهایی.
به همین دلیل هم اثر انگشتها اغلب پس از بهروزرسانی بزرگ مرورگر تغییر میکنند، و ردیابها برای پیوند دادن اثر انگشت قدیمی به جدید به سیگنالهای پایدار دیگر (صفحهنمایش، منطقهٔ زمانی، فونتها) تکیه میکنند.
چرا افزونههای تزریق نویز اغلب نتیجهٔ عکس میدهند
افزونههای زیادی به سبک «Canvas Defender» هست که پیش از بازگشت toDataURL اختلالهای تصادفی کوچکی به پیکسلها تزریق میکنند تا هر خواندن متفاوت باشد. منطقی به نظر میرسد. دو مشکل دارد.
اول، مرورگرهای واقعی پایدارند. اگر سایتی canvas را دو بار بکشد و دو هش بگیرد، فوراً میفهمد ابزار اختلال اجرا میکنید. امتیاز اصالت ما دقیقاً همین بررسی را دارد: دو رسم متفاوت باشد، امتیاز کم میشود. افراد کمی چنین ابزارهایی را اجرا میکنند، پس این سیگنال بهتنهایی شما را در گروه کوچکی قرار میدهد.
دوم، اگر افزونه بهجای آن یک انحراف ثابت اعمال کند (هر بار یکسان)، هش شما به چیزی تبدیل میشود که هیچکس دیگری در دنیا ندارد. این یکتاتر از مقدار بدون نویز است که دستکم با چند هزار MacBook یکسان مطابقت داشت. دقیقاً خلاف هدف.
رویکردهای پختهتر در خود مرورگرها ساخته شدهاند. privacy.resistFingerprinting در Firefox پیش از خواندن canvas اجازه میپرسد و پارامترهای WebGL را به مقادیر رایج نرمال میکند، با این هدف که همهٔ کسانی که این تنظیم را دارند یکسان به نظر برسند. «farbling» در Brave زیرکانهتر است: با seed مشتقشده از سایت بهعلاوهٔ نشست اختلال ایجاد میکند، بنابراین اثر انگشت شما داخل یک سایت پایدار است (بدون نوسان لودهنده) اما بین سایتها متفاوت (بدون امکان پیوند).
WebGPU بعدی است
جانشین WebGL، یعنی WebGPU، در Chrome عرضه شده و در زمان نوشتن این متن Safari و Firefox در حال رسیدن به آن هستند. اطلاعات adapter آن سازنده، معماری، دستگاه و توضیح، فهرست بلندی از محدودیتها (بیشینهٔ اندازهٔ buffer، ابعاد workgroup و مانند آن) و قابلیتهای اختیاری را نشان میدهد. اینها به سختافزار نزدیکترند تا معادلهایشان در WebGL، و compute shaderها اندازهگیریهای مبتنی بر زمانبندی را آسانتر میکنند. پژوهش اثر انگشتگیری از قبل آن را بهعنوان منبع تازهای پذیرفته است. گزارش WebGL ما هر وقت مرورگر پشتیبانی کند اطلاعات adapter در WebGPU را فهرست میکند تا ببینید چقدر لو میدهد.
پرسشهای متداول
نمیتوانم اثر انگشت canvas را کلاً خاموش کنم؟ میتوانید، اما بسیاری از چیزهای وابسته به canvas از کار میافتند (نمودارها، ویرایشگرهای تصویر، برخی CAPTCHAها)، و خودِ «خاموش بودن» قابل تشخیص است. مسیر عملی resistFingerprinting در Firefox یا Brave است: بهجای دنبال «نامرئی بودن»، «شبیه جمعیت بودن» را بپذیرید.
رندرکنندهٔ WebGL من SwiftShader یا llvmpipe نشان میدهد. یعنی چه؟ مرورگر بدون شتاب GPU، بهصورت نرمافزاری روی CPU رندر میکند. مرورگرهای دسکتاپ واقعی بهندرت این کار را میکنند؛ ماشینهای مجازی و مرورگرهای headless معمولاً میکنند، به همین دلیل یک سیگنال تشخیص ربات به حساب میآید.
در این سایت هش canvas من «با M مورد از N بازدید آخر مطابقت دارد». M بزرگ خوب است یا بد؟ بزرگ خوب است: شبیه افراد زیادی هستید و جدا کردنتان سخت است. M برابر ۱ یعنی هیچکس دیگری آن مقدار را تولید نکرده است.