آموزش تأیید سلامت فایل با sha256sum در لینوکس
با استفاده از دستور sha256sum فایلهای دانلود شده را بررسی کنید. در این راهنما یاد میگیرید چگونه با تغییر یک بایت و مشاهده خطای checksum، مفهوم واقعی یکپارچگی فایل را درک کنید.
تأیید یک دانلود با استفاده از checksum در دو دقیقه
برای تأیید یک فایل دانلود شده با استفاده از checksum، باید از فایلی که دریافت کردهاید hash بگیرید و اجازه دهید یک ابزار، آن hash را با مقداری که ناشر اعلام کرده است مقایسه کند. ابزار sha256sum هر دو بخش این کار را انجام میدهد: بهتنهایی یک digest چاپ میکند و با استفاده از فلگ -c، لیستی از digestها را میخواند و گزارش میدهد که کدام فایلها مطابقت دارند. این راهنما کل چرخه را روی فایلی که خودتان میسازید اجرا میکند و سپس آن فایل را عمداً تغییر میدهد تا بهجای خواندن درباره خرابی، وقوع آن را مشاهده کنید.
در تمام طول این مسیر، یک جمله را در ذهن داشته باشید. یک checksum به شما میگوید که آیا بایتهایی که در اختیار دارید همان بایتهایی هستند که digest را تولید کردهاند یا خیر، اما هیچ اطلاعاتی درباره اینکه چه کسی آنها را تولید کرده است به شما نمیدهد. پاسخ به آن پرسش دوم، نیازمند یک signature و یک key است که به آن اعتماد دارید. بخش پایانی این راهنما دقیقاً نشان میدهد که مرز بین این دو مفهوم کجا قرار دارد.
ایجاد یک فایل برای تمرین
در یک دایرکتوری موقت کار کنید تا هیچکدام از عملیات زیر بر سایر بخشهای سیستم تأثیر نگذارد. تمام دستورات زیر از مجموعه GNU coreutils هستند که در هر سرور Ubuntu یا Debian بهصورت پیشفرض وجود دارند، بنابراین نیازی به نصب هیچ ابزاری نیست.
mkdir -p ~/checksum-demo
cd ~/checksum-demo
printf 'the payload of a totally ordinary download\n' > payload.txt
sha256sum payload.txtشما یک خط خروجی دریافت میکنید: 64 کاراکتر هگزادسیمال، دو فاصله، و سپس نام فایل. آن 64 کاراکتر، digest (خلاصه) فایل هستند. دستور را دوباره اجرا کنید؛ خط خروجی دقیقاً مشابه قبلی است، زیرا هش کردن یک فرآیند قطعی (deterministic) است: ورودی یکسان همیشه خروجی یکسانی تولید میکند. یک کاراکتر از فایل را تغییر دهید و دستور را دوباره اجرا کنید؛ خواهید دید که digest تغییر جزئی نمیکند. خروجی کاملاً متفاوت به نظر میرسد، زیرا تغییر حتی یک بیت در ورودی، باعث تغییر حدود نیمی از بیتهای خروجی میشود. همین ویژگی است که یک رشته 64 کاراکتری را به جایگزینی قابلاطمینان برای یک فایل ایمیج 4 گیگابایتی تبدیل میکند.
ذخیره فایل SHA256SUMS و بررسی آن
یک هش (digest) که روی صفحه نمایش داده میشود، یک روز بعد دیگر کاربردی ندارد. آن را در فایلی ذخیره کنید، با همان فرمتی که sha256sum تولید میکند، تا ابزار بتواند بعداً آن را بازخوانی کند.
sha256sum payload.txt > SHA256SUMS
cat SHA256SUMS
sha256sum -c SHA256SUMSsha256sum -c هر خط از لیست را میخواند، فایل نامبرده در آن خط را هش میکند و دو هش را با هم مقایسه میکند. یک اجرای موفق برای هر فایل یک خط چاپ میکند:
payload.txt: OKوضعیت خروج (exit status) را نیز بررسی کنید، زیرا اسکریپتها آن را میخوانند و هرگز متن خروجی را بررسی نمیکنند. echo $? پس از یک اجرای موفق، 0 را چاپ میکند. نام SHA256SUMS بیشتر یک قرارداد است تا یک قانون، اما توزیعهای لینوکسی و اکثر صفحات انتشار از آن استفاده میکنند؛ بنابراین شما هم از همین نام استفاده کنید تا نفر بعدی بدون باز کردن فایل، بداند محتوای آن چیست.
تغییر یک بایت و مشاهده شکست بررسی
اکنون فایل را عمداً خراب کنید. این دستور یک بایت را در offset شماره 5 مینویسد و بقیه فایل را دستنخورده باقی میگذارد، بنابراین طول و نام فایل تغییر نمیکند.
printf 'X' | dd of=payload.txt bs=1 seek=5 conv=notrunc status=none
cat payload.txt
sha256sum -c SHA256SUMSفلگ conv=notrunc در اینجا اهمیت دارد: بدون آن، dd فایل را در نقطهای که نوشتن متوقف میشود کوتاه (truncate) میکند و شما نوع بسیار آشکارتری از خرابی را تست خواهید کرد. بررسی اکنون این خروجی را چاپ میکند:
payload.txt: FAILED
sha256sum: WARNING: 1 computed checksum did NOT matchecho $? مقدار 1 را چاپ میکند. FAILED به این معنی است که فایل خوانده شده اما digest آن با مقدار موجود در لیست مطابقت نداشته است. بایتهای اصلی را بازگردانید و تأیید کنید که بررسی به OK بازمیگردد:
printf 'the payload of a totally ordinary download\n' > payload.txt
sha256sum -c SHA256SUMSاین تمام روال کار است. یک بایت تفاوت در هر جای فایل، منجر به FAILED میشود. دانلودی که به دلیل قطع اتصال ناقص مانده، میروری که نسخه دیروز را ارائه میدهد، پروکسی که فایل را در حین انتقال بازنویسی کرده، یا دیسکی که یک بلاک خراب برگردانده است: همگی به همین نتیجه ختم میشوند.
هنگامی که لیست شامل فایلی است که دانلود نکردهاید
یک فایل SHA256SUMS واقعی از یک توزیع، تمام ایمیجهایی را که پروژه منتشر میکند فهرست میکند، در حالی که شما فقط یکی از آنها را دانلود کردهاید. این وضعیت را در اینجا بازسازی کنید.
printf 'a second file\n' > notes.txt
sha256sum payload.txt notes.txt > SHA256SUMS.all
rm notes.txt
sha256sum -c SHA256SUMS.allpayload.txt: OK
sha256sum: notes.txt: No such file or directory
notes.txt: FAILED open or read
sha256sum: WARNING: 1 listed file could not be readFAILED open or read یک خطای متفاوت از FAILED است و اشتباه گرفتن این دو باعث اتلاف وقت میشود. FAILED به این معنی است که بایتها نادرست هستند. FAILED open or read به این معنی است که sha256sum اصلاً فایل را دریافت نکرده است، بنابراین هیچ مقایسهای انجام نشده است. در یک دانلود واقعی، علت معمول، دایرکتوری کاری است، زیرا نامهای موجود در لیست نسبت به جایی که دستور را اجرا میکنید، نسبی هستند. به دایرکتوری حاوی فایل بروید و دستور را دوباره اجرا کنید. برای بررسی فقط آنچه واقعاً در اختیار دارید، اینگونه عمل کنید:
sha256sum --ignore-missing -c SHA256SUMS.allاین دستور payload.txt: OK را چاپ کرده و با کد خروج 0 پایان مییابد. اگر هیچیک از نامهای فهرستشده موجود نباشد، --ignore-missing بهطور بیسروصدا روی صفر فایل موفق نمیشود. این دستور گزارش میدهد که no file was verified و با کد خروج غیر صفر پایان مییابد، که این همان رفتاری است که انتظار دارید؛ زیرا یک بررسی موفق که هیچ فایلی را چک نکرده باشد، خطایی است که هرگز متوجه آن نخواهید شد.
جایگذاری یک digest منتشرشده بدون بررسی چشمی
مقایسه 64 کاراکتر هگزادسیمال با چشم، همان جایی است که این عادت امنیتی شکست میخورد. افراد معمولاً چهار کاراکتر اول و چهار کاراکتر آخر را چک میکنند و آن را یک تطابق مینامند؛ این دقیقاً همان نوع مقایسهای است که یک مهاجم مصمم برای آن برنامهریزی میکند. اجازه دهید ابزار این مقایسه را انجام دهد. مقدار EXPECTED را روی digest که از ناشر کپی کردهاید تنظیم کنید، با استفاده از EXPECTED= و سپس مقدار جایگذاریشده، و در نهایت خط واحدی که -c انتظار دارد را بسازید:
printf '%s %s\n' "$EXPECTED" payload.txt > payload.sha256
cat payload.sha256
sha256sum -c payload.sha256دو فاصله بین digest و نام فایل قرار میگیرد، به همین دلیل است که رشته فرمت شامل دو فاصله است. این همان شکلی است که sha256sum مینویسد و -c آن را تجزیه (parse) میکند. فایلی که فقط شامل یک digest باشد، اصلاً یک خط checksum محسوب نمیشود؛ بنابراین ابزار بررسی، کل فایل را با خطای no properly formatted checksum lines found رد میکند تا حدس نزند منظور شما کدام فایل بوده است. برخی پروژهها در عوض از سبک BSD tagged استفاده میکنند، یعنی SHA256 (payload.txt) = به دنبال آن digest. ابزار GNU coreutils این فرمت را با sha256sum --tag payload.txt مینویسد و با -c میخواند، بنابراین ذخیره کردن هر دو شکل مناسب است.
هنگامی که یک بررسی رفتار عجیبی دارد، لیست را با cat -A SHA256SUMS مشاهده کنید؛ این دستور انتهای هر خط را با $ علامتگذاری میکند و کاراکترهایی که در حالت عادی قابل دیدن نیستند را نمایش میدهد. خطی که به ^M$ ختم میشود، یک carriage return از یک ویرایشگر ویندوزی دریافت کرده است. ابزار GNU sha256sum این کاراکتر انتهایی را نادیده میگیرد و همچنان OK را چاپ میکند، بنابراین یک لیست با فرمت CRLF عامل شکستن بررسی شما نیست، اگرچه ابزارهای خارج از coreutils در این مورد سختگیرتر هستند. نسخهای که نگه میدارید را با tr -d '\r' < SHA256SUMS > SHA256SUMS.clean نرمالسازی کنید.
چکسام (checksum) چه چیزی را اثبات میکند و چه چیزی را خیر؟
یک چکسام تنها یک چیز را اثبات میکند: بایتهای موجود روی دیسک شما، همان بایتهایی هستند که خروجی (digest) منتشرشده را تولید کردهاند. این موضوع آسیبهای تصادفی را بهطور کامل پوشش میدهد. همچنین، این مورد مهاجم بیدقتی را که فایل را در یک mirror دانلود جایگزین کرده اما به صفحهای که خروجی را منتشر کرده دسترسی نداشته، خنثی میکند.
این مورد هیچ چیزی را دربارهٔ هویت نویسنده اثبات نمیکند. یک خروجی، واقعیتی دربارهٔ بایتهاست، نه واقعیتی دربارهٔ اشخاص. اگر یک صفحه هم فایل و هم خروجی را ارائه دهد، هر کسی که بتواند یکی را تغییر دهد، میتواند دیگری را نیز تغییر دهد و خط OK شما تنها به این معناست که آن mirror با خودش همخوانی دارد. بنابراین، قانونی که چکسامها را ارزشمند میکند این است: خروجی را از جایی غیر از محل دریافت فایل تهیه کنید. برای مثال، دامنهٔ اصلی پروژه از طریق TLS (امنیت لایه انتقال) در حالی که ایمیج از یک mirror یا تورنت دریافت شده است. اکنون مهاجم باید به جای یک مکان، دو مکان را کنترل کند.
الگوریتم نیز اهمیت دارد. SHA-256 (الگوریتم هش امن، با خروجی 256 بیتی) تا اوت 2026 هیچ برخورد (collision) شناختهشدهای ندارد و به همین دلیل ناشران از آن استفاده میکنند. MD5 (پیام خلاصه 5) و SHA-1 دیگر قابلاعتماد نیستند: ساخت دو فایل متفاوت با خروجی MD5 یکسان از سال 2004 ممکن شده است و یک برخورد با پیشوند انتخابی برای SHA-1 در سال 2020 منتشر شد. یک فایل MD5SUMS همچنان دانلودهای ناقص را شناسایی میکند، زیرا خرابی تصادفی، یک برخوردِ مهندسیشده نیست. اما این روش نمیتواند جلوی کسی که قصد فریب شما را دارد بگیرد. هنگامی که یک پروژه هر دو را منتشر میکند، خط SHA-256 را انتخاب کنید.
جایی که امضاها کنترل را به دست میگیرند
یک امضا، شکافی را که یک digest باقی میگذارد، پر میکند. ناشر فایل digest را با یک کلید خصوصی امضا میکند و شما آن را با کلید عمومی او بررسی میکنید: gpg --verify SHA256SUMS.asc SHA256SUMS. اگر این بررسی موفقیتآمیز باشد، لیست digestها از طرف کسی آمده است که آن کلید را در اختیار دارد. سپس sha256sum -c SHA256SUMS فایلی که روی دیسک شما قرار دارد را به آن لیست متصل میکند و زنجیره اعتماد از کلید تا آخرین بایت فایل برقرار میشود.
نقطه ضعف به سمت کلید منتقل میشود. دریافت کلید از همان صفحهای که فایل را ارائه داده است، هر دو بخش را در اختیار مهاجم قرار میدهد. GnuPG در این مورد صادق است و در اولین تایید، Good signature را به همراه WARNING: This key is not certified with a trusted signature! نمایش میدهد. Good signature به این معنی است که محاسبات ریاضی درست است. این به معنای آن نیست که کلید متعلق به پروژهای است که شما در نظر دارید. اثر انگشت (fingerprint) را از یک منبع دوم دریافت کنید، مانند مستندات پروژه در یک دامنه متفاوت یا یک بسته توزیع که از قبل کلید را به همراه دارد، و به جای هشت کاراکتر آخر، کل اثر انگشت را مقایسه کنید. این همان دقتی است که یک کلید خصوصی SSH سزاوار آن است، به همان دلیل: کلید، تصمیم نهایی در مورد اعتماد است و هر چیزی که در ادامه میآید، آن را به ارث میبرد.
ساختهای قابل بازتولید (Reproducible builds) این ایده را یک گام جلوتر میبرند. یک digest منتشر شده، همچنان شما را به باینریای متصل میکند که توسط یک ماشین ساخته شده است. وقتی ساخت یک پروژه قابل بازتولید باشد، هر کسی میتواند همان سورسکد را کامپایل کند و خروجیای کاملاً یکسان (از نظر بایت) دریافت کند؛ بنابراین سازندگان مستقل میتوانند به جای اینکه از شما بخواهند به حرف یک سرور اعتماد کنید، digest منتشر شده را تایید کنند. این موضوع هر سال اهمیت بیشتری پیدا میکند، چرا که کدهای بیشتری از طریق خط لولههای خودکار و وصلههای نوشتهشده توسط ماشین وارد میشوند. تصمیمگیری در مورد آنچه در یک ساخت میپذیرید، یک مسئله سیاستگذاری است و سیاستهای متنباز برای کدهای کمکگرفته از هوش مصنوعی از سمت دیگر، روی همین زنجیره تامین کار میکنند.
مدیریت بسته شما این کار را بهصورت خودکار انجام میدهد
در توزیعهای Debian و Ubuntu، ابزار apt این زنجیره را در هر نصب بدون نیاز به درخواست کاربر اجرا میکند. فهرست بستهها شامل یک digest از نوع SHA-256 برای هر فایل .deb است. فایل Release حاوی digestهای مربوط به آن فایلهای فهرست است و InRelease یک امضا روی Release دارد که با کلیدهای موجود در /usr/share/keyrings و /etc/apt/trusted.gpg.d تطبیق داده میشود. هنگامی که این زنجیره قطع شود، apt آن را اعلام میکند: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY زمانی که کلید یک مخزن شخص ثالث موجود نباشد، یا Hash Sum mismatch زمانی که فهرستی که دریافت کردهاید با فایل امضاشده Release مطابقت نداشته باشد؛ این وضعیت معمولاً به این معناست که یک caching proxy فایل قدیمی را ارائه داده یا شما در حین همگامسازی (sync) یک mirror، دادهها را دریافت کردهاید.
این استانداردی است که باید هنگام مواجهه با وبسایت پروژههایی که پیشنهاد میدهند اسکریپتی را از curl مستقیماً به یک shell پایپ (pipe) کنید، در نظر بگیرید. در آن روش، هیچچیز بایتها را تایید نمیکند و شما هرگز آنها را نمیبینید. سرور همچنین میتواند یک محتوا را به اسکریپت و محتوای دیگری را به مرورگر ارائه دهد و شما هیچ نسخهای برای بررسی پس از آن ندارید. فایل را با curl -fsSL <url> -o install.sh دانلود کنید، آن را hash کنید، با less محتوای آن را بخوانید و تنها پس از آن اجرا کنید. این عادت حدود 20 ثانیه زمان میبرد و همان عادتی است که ارزش دارد در یک VPS کاملاً جدید در ده دقیقه اول، پیش از نصب هر چیز دیگری روی سرور، آن را آغاز کنید.
فهرستی از هشها برای فایلهایی که دستی نصب میکنید نگه دارید
بستههایی که توسط apt نصب میشوند، ردیابی میشوند. فایلی باینری که آن را در /usr/local/bin کپی کردهاید، ردیابی نمیشود و هیچ بخشی از سیستم آن را نظارت نمیکند. داشتن فهرستی از هشها (digests) این وضعیت را به چیزی تبدیل میکند که میتوانید در صورت نیاز آن را بررسی کنید:
printf 'a second file\n' > notes.txt
sha256sum *.txt > inventory.sha256
sha256sum -c --quiet inventory.sha256--quiet زمانی که همه فایلها مطابقت داشته باشند، چیزی چاپ نمیکند و تنها در صورت عدم مطابقت، خطوطی که با شکست مواجه شدهاند را نمایش میدهد؛ بنابراین سکوت به معنای موفقیت است و echo $? این موضوع را با 0 تأیید میکند. این همان قالبی است که باید در یک job زمانبندیشده قرار دهید. --status فراتر میرود و هیچ خروجی متنی چاپ نمیکند و فقط وضعیت خروج (exit status) را برای شما باقی میگذارد. همین الگو را با sha256sum /usr/local/bin/* > ~/local-bin.sha256 به فایلهای واقعی ارجاع دهید تا یک مبنا (baseline) داشته باشید. مسیرها دقیقاً همانطور که تایپ کردهاید در فهرست ذخیره میشوند، بنابراین استفاده از مسیرهای مطلق (absolute paths) باعث میشود بررسی از هر دایرکتوری به درستی کار کند.
درباره ارزش این مبنا واقعبین باشید. این روش تغییر یک فایل را تشخیص میدهد. اما مهاجمی که دسترسی root دارد را شناسایی نمیکند، زیرا آن مهاجم میتواند inventory.sha256 را به همان سادگی که فایل باینری را بازنویسی کرده، تغییر دهد. اگر میخواهید این فهرست اعتبار داشته باشد، آن را خارج از ماشین نگهداری کنید؛ این بخشی از پرسش گستردهتر درباره میزان اعتمادی که به یک VPS دارید و این است که چه کسانی دیگر میتوانند به دیسک زیرین آن دسترسی داشته باشند.
FAQ
آیا تطابق checksum به معنای امن بودن فایل دانلود شده است؟
خیر. این تنها به این معناست که بایتهای فایل شما با مقدار digest که با آن مقایسه کردهاید، مطابقت دارد. اگر مهاجم صفحهای که digest را منتشر کرده کنترل کند، میتواند digest فایل مخرب خود را منتشر کند و بررسی شما نیز OK را چاپ خواهد کرد. تطابق، تنها ادعای سازگاری است. ادعای امنیت نیازمند امضایی است که با کلیدی از منبعی متفاوت تأیید شده باشد؛ تنها در این صورت است که digest آن اعتبار را به ارث میبرد.
چرا sha256sum -c پیام FAILED open or read را چاپ میکند؟
زیرا فایل خوانده نشده است. یک خط جداگانه درست بالای آن، نام فایلی که به دنبالش بوده را با No such file or directory نشان میدهد. نامهای موجود در فایل SHA256SUMS نسبت به دایرکتوری که دستور را در آن اجرا میکنید سنجیده میشوند؛ بنابراین به دایرکتوری حاوی فایل دانلود شده بروید و دستور را دوباره اجرا کنید. اگر لیست شامل فایلهایی است که دانلود نکردهاید، از --ignore-missing استفاده کنید. یک FAILED ساده بدون open or read وضعیت متفاوتی دارد: فایل خوانده شده، اما digest آن مطابقت نداشته است.
آیا MD5 برای تأیید یک دانلود کافی است؟
برای آسیبهای تصادفی، بله. یک انتقال ناقص یا یک بلاک دیسک خراب، بهطور تصادفی مقدار MD5 یکسانی تولید نمیکند. اما در برابر یک مهاجم، خیر. از سال 2004 امکان ساخت دو فایل متفاوت با MD5 یکسان وجود دارد و SHA-1 نیز در سال 2020 در برابر حملات collision از نوع chosen-prefix شکست خورد. اگر پروژهای هر دو را منتشر میکند، خط SHA-256 را انتخاب کنید و پروژهای که فقط MD5 ارائه میدهد را به عنوان نشانهای از یک فرایند انتشار قدیمی در نظر بگیرید.
تفاوت بین sha256sum -c و gpg --verify چیست؟
sha256sum -c ثابت میکند که یک فایل با یک digest مطابقت دارد. gpg --verify ثابت میکند که فایل digest توسط دارنده یک کلید خصوصی خاص امضا شده است. این دو به پرسشهای متفاوتی پاسخ میدهند، بنابراین وقتی پروژهای هر دو را ارائه میدهد، هر دو را اجرا کنید. امضا باعث میشود لیست digest قابل اعتماد باشد و لیست digest سپس فایل دانلود شده را قابل اعتماد میکند.
چگونه یک فایل را با digest چاپ شده در یک صفحه وب بررسی کنم؟
کاراکترها را با چشم مقایسه نکنید. digest و نام فایل را در یک خط واحد ذخیره کنید، آنها را با دو فاصله از هم جدا کنید، سپس sha256sum -c را روی آن فایل اجرا کرده و OK یا FAILED چاپ شده را بخوانید. ساختن این خط با printf '%s %s\n' از خطاهای فرمتبندی که باعث میشود sha256sum فایل را با no properly formatted checksum lines found رد کند، جلوگیری میکند.