بررسی یوزرنیم · یک آزمایش ساده با حروف واقعی متن

این دو یوزرنیم شبیه‌اند؛ از کجا بفهمیم حروفشان یکی است؟

فرض کنید دو نام کاربری دیده‌اید: maple و mаple. شاید در نگاه اول هیچ فرقی نبینید. اما حرف دوم در یکی لاتین است و در دیگری سیریلیک. برای پیدا کردن این تفاوت، بزرگ‌کردن عکس صفحه همیشه کافی نیست؛ باید خودِ متن را بررسی کنیم.

یونیکد کمک می‌کند دقیق بگوییم چه حروفی در یک نام وجود دارند. این کار قرار نیست صاحب حساب را شناسایی کند. قرار است پیش از آنکه دو پروفایل را به هم ربط بدهیم، مطمئن شویم چه چیزی را با چه چیزی مقایسه کرده‌ایم.

دو شکل سرامیکی شبیه که تفاوت قطعه‌های سازنده آن‌ها زیر ذره‌بین دیده می‌شود
تصویر مفهومی: ظاهر نزدیکِ دو شکل، یکسان‌بودن اجزای آن‌ها را ثابت نمی‌کند. این تصویر مربوط به حساب واقعی نیست.

اول متن اصلی را نگه دارید

قبل از اصلاح فاصله‌ها یا تبدیل حروف، یک نسخه دست‌نخورده از یوزرنیم ذخیره کنید. کنارش آدرس عمومی پروفایل، نام پلتفرم و زمان مشاهده را بنویسید. مشخص کنید متن را از فیلد نام کاربری برداشته‌اید یا از نام نمایشی بالای پروفایل. نام نمایشی ممکن است تزئینی باشد و اصلاً نقش آدرس حساب را نداشته باشد.

اگر فقط اسکرین‌شات دارید، صادقانه بنویسید «از روی تصویر خوانده شده». تایپ دوباره و OCR هر دو یک متن تازه می‌سازند؛ نمی‌توان با اطمینان گفت تمام نویسه‌های پشت تصویر را عیناً حفظ کرده‌اند. برای آزمایش، روی کپی دوم کار کنید تا تفاوت اولیه در نسخه اصلی باقی بماند.

این مرحله با پیدا کردن حساب‌های جدید فرق دارد. مسیر کلیِ جست‌وجو را در راهنمای تحقیق با یوزرنیم می‌بینید؛ اینجا می‌خواهیم دو متنی را که در دست داریم، درست بخوانیم.

حرفی که می‌بینیم، یک شماره هم دارد

در یونیکد، هر جایگاهِ نویسه یک شماره دارد. به آن «کدپوینت» می‌گویند و مثلاً به شکل U+0061 می‌نویسند. در مثال بالا، a لاتین همین شماره را دارد؛ а سیریلیک شماره‌اش U+0430 است. شکل نزدیک، این دو شماره را یکی نمی‌کند.

گاهی ماجرا برعکس است: یک حرفِ ظاهراً واحد از دو جزء ذخیره‌شده ساخته می‌شود. برای نمونه، حرف آخرِ café می‌تواند یک نویسه آماده باشد یا ترکیب e و علامت تکیه. برای مقایسه چنین حالت‌هایی، یونیکد روش‌های مشخصی به نام نرمال‌سازی دارد.

در استاندارد نرمال‌سازی یونیکد، NFC نمایش‌های هم‌ارزِ کانونی را یکسان می‌کند. NFKC بعضی تفاوت‌های سازگاری، مثل حروف تمام‌عرضِ مثال ما، را هم برمی‌دارد. هیچ‌کدام قرار نیست املای موردنظر نویسنده یا هویت صاحب حساب را حدس بزنند.

چهار مثال را واقعاً آزمایش کردیم

جدول زیر حاصل اجرای محلی چهار جفت متن آموزشی است؛ نه بررسی حساب واقعی. محیط اجرا Python 3.12.10 و پایگاه یونیکد 15.0.0 بود. در هر ستون، «بله» یعنی دو رشته در همان نوع مقایسه برابر شدند.

دو متن نمونهبرابری متن خامپس از NFCپس از NFKC
maple و mаple با حرف سیریلیکخیرخیرخیر
caf\u00e9 و cafe\u0301خیربلهبله
maple و شکل تمام‌عرضِ mapleخیرخیربله
«مینا» با ی فارسی و «مينا» با ی عربیخیرخیرخیر

ردیف آخر برای متن فارسی کاربرد زیادی دارد: حتی NFKC هم در این آزمایش «ي» عربی را به «ی» فارسی تبدیل نکرد. اگر برای بهتر جست‌وجوکردن، هر دو املای نام را امتحان می‌کنید، آن را «گسترش جست‌وجو با املای جایگزین» بنامید؛ متن اصلی را بی‌سروصدا عوض نکنید.

کد آزمایش را ببینید و روی رایانه خودتان اجرا کنید

کد را با نام unicode_compare.py ذخیره کنید و دستور python unicode_compare.py را اجرا کنید. این برنامه فقط از کتابخانه استاندارد پایتون استفاده می‌کند. نتیجه کوتاه چاپ می‌شود و جزئیات حروف و نسخه محیط در فایل unicode-results.json کنار برنامه قرار می‌گیرد. هیچ جست‌وجوی اینترنتی انجام نمی‌شود.

"""Offline teaching fixture; no account lookup, network call or input cleanup."""
import json
import platform
import unicodedata as ud
from datetime import datetime, timezone
from pathlib import Path

fixtures = [
    ('Latin versus Cyrillic', 'maple', 'm\u0430ple'),
    ('Composed versus combining', 'caf\u00e9', 'cafe\u0301'),
    ('Width variant', 'maple', '\uff4d\uff41\uff50\uff4c\uff45'),
    ('Persian versus Arabic Yeh', '\u0645\u06cc\u0646\u0627', '\u0645\u064a\u0646\u0627'),
]

def describe(text):
    return [{'codepoint': f'U+{ord(c):04X}',
             'name': ud.name(c, 'UNNAMED')} for c in text]

rows = []
for label, left, right in fixtures:
    row = {'case': label, 'left': left, 'right': right,
           'left_characters': describe(left),
           'right_characters': describe(right),
           'equal_raw': left == right,
           'equal_NFC': ud.normalize('NFC', left) == ud.normalize('NFC', right),
           'equal_NFKC': ud.normalize('NFKC', left) == ud.normalize('NFKC', right)}
    rows.append(row)
    print(label, row['equal_raw'], row['equal_NFC'], row['equal_NFKC'])

report = {'checked_at_utc': datetime.now(timezone.utc).isoformat(),
          'python': platform.python_version(), 'unicode_database': ud.unidata_version,
          'scope': 'Four synthetic string pairs; no platform behavior or identity tested',
          'rows': rows}
Path(__file__).with_name('unicode-results.json').write_text(
    json.dumps(report, ensure_ascii=False, indent=2), encoding='utf-8')

نام و شماره حروف را در خروجی جزئیات ببینید. توضیح توابع در مستندات رسمی unicodedata پایتون آمده است؛ نسخه مستندات آنلاین ممکن است از پایتون نصب‌شده شما جدیدتر باشد.

نتیجه جدول را به حساب کاربری تعمیم ندهید

اگر متن خام برابر شد، فقط می‌دانیم دنباله نویسه‌ها یکی است. همان نام در دو شبکه اجتماعی می‌تواند متعلق به دو نفر باشد. اگر متن خام متفاوت بود ولی NFC جواب برابر داد، شکل ذخیره‌سازی متفاوت بوده است؛ اینکه پلتفرم با این دو ورودی چه رفتاری دارد، هنوز باید از قواعد همان پلتفرم روشن شود.

وقتی فقط NFKC دو متن را برابر می‌کند، نسخه‌های اصلی را نگه دارید و علت تفاوت را ثبت کنید. نتیجه آزمایش مجوز حدس‌زدن یک آدرس تازه برای پروفایل نیست. اگر هیچ مقایسه‌ای برابر نشد ولی ظاهر شبیه بود، بررسیِ حروف متفاوت تازه شروع کار است؛ از آن نمی‌توان جعل هویت را نتیجه گرفت.

راهنمای امنیتی یونیکد درباره تشخیص نویسه‌های اشتباه‌گرفتنی توضیح می‌دهد. این موضوع با NFC و NFKC یکی نیست. برنامه کوتاه ما آشکارساز کاملِ حروف مشابه یا آزمون قوانین ثبت‌نام شبکه‌های اجتماعی نیست.

وجود خط غیرلاتین، ترکیب چند خط یا نویسه‌های پیونددهنده به‌خودی‌خود نشانه بدی نیست. مثلاً نیم‌فاصله در فارسی کاربرد عادی دارد. پرسش دقیق‌تر این است: آیا تفاوتِ مشاهده‌شده، اشتباه خاصی را که در این تحقیق رخ داده توضیح می‌دهد؟

یادداشتی بنویسید که نفر بعد بتواند بررسی کند

برای جفت اولِ مثال آموزشی می‌توان نوشت:

حرف دومِ دو متن ارسالی متفاوت است: U+0061 در برابر U+0430. در محیط ثبت‌شده، پس از NFC و NFKC هم برابر نشدند. ممکن است شبیه دیده شوند؛ درباره مالک حساب‌ها از این آزمایش نتیجه‌ای نداریم.

در پرونده واقعی، آدرس منبع و زمان برداشت را به این یادداشت اضافه کنید. اگر مسئله شما تغییر نامِ یک حساب در طول زمان است، بررسی سابقه تغییر یوزرنیم شواهد دیگری لازم دارد.

تا وقتی مدرک کافی ندارید، دو پروفایل را در یادداشت‌های تحقیق جدا نگه دارید. گردش کار گراف هویت دیجیتال اوسینت جت برای مرتب‌کردن ارتباط‌ها و بررسی آن‌ها معرفی شده است. یادداشتِ تفاوت حروف باید به روشن‌شدن ابهام کمک کند؛ شباهت ظاهری نباید در مسیر تنظیم گزارش به «هویت تأییدشده» تبدیل شود.

منتشرشده توسط تحریریه اوسینت جت · ۵ اکتبر ۲۰۲۶

پیشنهاد اصلاح · English version