آموزش تأیید سلامت فایل در لینوکس با دستور sha256sum
با استفاده از دستور sha256sum فایلهای دانلود شده را بررسی کنید. در این راهنما با تغییر عمدی یک بایت و مشاهده خطای FAILED، نحوه کارکرد دقیق checksum را در لینوکس میآموزید.
تأیید یک دانلود با استفاده از checksum در دو دقیقه
برای تأیید یک فایل دانلود شده با استفاده از checksum، باید از فایلی که دریافت کردهاید hash بگیرید و اجازه دهید یک ابزار، آن hash را با مقداری که ناشر منتشر کرده است مقایسه کند. sha256sum هر دو بخش این کار را انجام میدهد: بهتنهایی یک digest چاپ میکند و با استفاده از -c، فهرستی از digestها را میخواند و گزارش میدهد که کدام فایلها مطابقت دارند. این راهنما کل چرخه را روی فایلی که خودتان میسازید اجرا میکند و سپس آن فایل را عمداً خراب میکند تا بهجای خواندن دربارهٔ شکست، وقوع آن را مشاهده کنید.
در تمام طول این مسیر، یک جمله را در ذهن داشته باشید. یک checksum به شما میگوید که آیا بایتهایی که در اختیار دارید همان بایتهایی هستند که digest را تولید کردهاند یا خیر، اما دربارهٔ اینکه چه کسی آنها را تولید کرده است، هیچ اطلاعاتی نمیدهد. پاسخ به آن پرسش دوم، نیازمند یک امضا و کلیدی است که به آن اعتماد دارید. بخش پایانی این راهنما دقیقاً نشان میدهد که مرز بین این دو در کجا قرار دارد.
ایجاد یک فایل برای تمرین
در یک دایرکتوری موقت کار کنید تا هیچکدام از این عملیات بر سایر بخشهای سیستم تأثیر نگذارد. تمام دستورات زیر از 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 و بررسی آن
نمایش هش روی صفحه نمایش، یک روز بعد کاربردی ندارد. آن را در فایلی با فرمتی که خود sha256sum تولید میکند بنویسید تا ابزار بتواند بعداً آن را بازخوانی کند.
sha256sum payload.txt > SHA256SUMS
cat SHA256SUMS
sha256sum -c SHA256SUMSsha256sum -c هر خط از لیست را میخواند، فایل نامبرده در آن خط را هش میکند و دو هش را با هم مقایسه میکند. اجرای موفق، برای هر فایل یک خط چاپ میکند:
payload.txt: OKوضعیت خروج (exit status) را نیز بررسی کنید، زیرا اسکریپتها آن را میخوانند و متن خروجی را پردازش نمیکنند. echo $? پس از یک اجرای موفق، 0 را چاپ میکند. نام SHA256SUMS بیشتر یک قرارداد است تا یک قانون، اما توزیعهای لینوکسی و اکثر صفحات انتشار از آن استفاده میکنند؛ بنابراین شما هم از همین نام استفاده کنید تا نفر بعدی بدون باز کردن فایل، بداند محتوای آن چیست.
تغییر یک بایت و مشاهده شکست بررسی
اکنون فایل را عمداً خراب کنید. این دستور یک بایت را در آفست 5 مینویسد و بقیه فایل را دستنخورده باقی میگذارد، بنابراین طول و نام فایل تغییر نمیکند.
printf 'X' | dd of=payload.txt bs=1 seek=5 conv=notrunc status=none
cat payload.txt
sha256sum -c SHA256SUMSconv=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 آن را تحلیل میکند. فایلی که فقط حاوی یک 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 یا تورنت دریافت شده باشد. اکنون مهاجم باید به جای یک مکان، دو مکان را کنترل کند. این موضوع همچنین درباره عملکرد آن بایتهای تأییدشده پس از اجرا چیزی نمیگوید؛ پرسش جداگانهای که ارزش پرسیدن درباره هر چیزی که به نمایندگی از شما اجرا میشود را دارد، از یک اسکریپت نصب گرفته تا یک پلاگین dsh که با مجوزهای agent شما اجرا میشود.
الگوریتم نیز اهمیت دارد. 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 منتشرشده را تایید کنند، بهجای اینکه از شما بخواهند فقط به حرف یک سرور اعتماد کنید. این موضوع هر سال اهمیت بیشتری پیدا میکند، چرا که کدهای بیشتری از طریق خط لولههای خودکار و وصلههای نوشتهشده توسط ماشین وارد میشوند. تصمیمگیری در مورد آنچه در یک build میپذیرید، یک مسئله سیاستگذاری است و سیاستهای متنباز برای کدهای کمکگرفته از هوش مصنوعی از سمت دیگر، روی همین زنجیره تأمین کار میکنند.
مدیریت بسته شما این کار را بهصورت خودکار انجام میدهد
در توزیعهای Debian و Ubuntu، ابزار apt این زنجیره را در هر نصب بدون نیاز به درخواست کاربر اجرا میکند. فهرست بستهها (package index) برای هر فایل .deb یک هش SHA-256 به همراه دارد. فایل Release حاوی هشهای مربوط به آن فایلهای فهرست است و 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 فایل قدیمی را ارائه داده یا شما در حال دریافت داده از یک mirror در حین عملیات همگامسازی بودهاید.
این استانداردی است که باید هنگام مواجهه با پروژههایی که در صفحه اصلی خود از شما میخواهند اسکریپتی را از curl مستقیماً به یک shell پایپ (pipe) کنید، مد نظر قرار دهید. در آن روش، هیچچیز بایتها را تایید نمیکند و شما هرگز آنها را نمیبینید. سرور همچنین میتواند یک محتوا را به اسکریپت و محتوای دیگری را به مرورگر ارائه دهد و شما هیچ نسخهای برای بررسی پس از اجرا نخواهید داشت. فایل را با curl -fsSL <url> -o install.sh دانلود کنید، هش آن را بگیرید، محتوای آن را با 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 برای تایید دانلود کافی است؟
برای آسیبهای تصادفی، بله. یک انتقال ناقص یا یک بلاک دیسک خراب به صورت تصادفی digest یکسان MD5 تولید نمیکند. اما در برابر مهاجم، خیر. از سال 2004 امکان ساخت دو فایل متفاوت با digest MD5 یکسان وجود داشته است و SHA-1 نیز در سال 2020 در برابر حملات 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 رد کند، جلوگیری میکند.