كيف تتأكد أن ملفًا حمّلته من الإنترنت لم يتغير أثناء النقل أو بعد حفظه على جهازك؟ إحدى الطرق العملية هي حساب بصمة SHA-256 للملف باستخدام بايثون، ثم مقارنتها بالقيمة التي نشرها المصدر الرسمي للملف.
في هذا الدرس من سلسلة بايثون بالمثال 26 سنكتب كودًا يعمل مباشرة باستخدام مكتبة hashlib، ونطبقه على ملفات صغيرة وكبيرة، ثم نحوله إلى أداة بسيطة تحسب البصمات وتتحقق منها وتقارن الملفات وتنشئ تقرير CSV.
{alertInfo}لا تحتاج إلى pip install. المكتباتhashlibوpathlibوcsvالتي سنستخدمها مدمجة مع بايثون. الأمثلة الأساسية تعمل على Python 3.8 أو أحدث.
حمّل أكواد الدرس والملفات التجريبية والاختبارات، وطبّق الأمثلة خطوة بخطوة على جهازك.
تحميل ملفات الدرس ZIP{getToc} $title={محتوى المقال}
ما هي بصمة SHA-256 للملف؟
تنتج خوارزمية SHA-256 قيمة ثابتة طولها 256 بت، وتظهر عادة على شكل 64 رمزًا سداسيًا. تحسب البصمة من محتوى الملف نفسه، لا من اسمه أو امتداده. ولهذا يمكن لملفين باسمين مختلفين أن يحملا البصمة نفسها إذا تطابقت بايتاتهما.
عندما يتغير المحتوى، تتغير البصمة عادة بدرجة كبيرة. وتستخدم البصمات لاكتشاف تغيّر البيانات أو تلفها، لكنها لا تثبت وحدها أن الملف آمن من البرمجيات الخبيثة، ولا أن مصدره موثوق.
| المصطلح | المعنى في هذا الدرس |
|---|---|
SHA-256 | الخوارزمية التي تنتج بصمة المحتوى |
hashlib | مكتبة بايثون المدمجة لحساب البصمات |
checksum | قيمة مرجعية تُستخدم للتحقق من المحتوى |
hexdigest() | تحويل نتيجة البصمة إلى نص سداسي مقروء |
قبل البدء: تجهيز الملفات التجريبية
أنشئ مجلدًا اسمه sha256_example، ثم ملفًا باسم make_demo_files.py. سيجهز البرنامج ملفات آمنة بأسماء مختلفة؛ اثنان منها متطابقان تمامًا، والثالث يختلف في حرف واحد.
"""إنشاء ملفات تجريبية تحتوي على بيانات غير حساسة."""
from pathlib import Path
folder = Path("samples")
folder.mkdir(exist_ok=True)
(folder / "original.txt").write_bytes(b"Python SHA256 demo\n")
(folder / "copy.txt").write_bytes(b"Python SHA256 demo\n")
(folder / "modified.txt").write_bytes(b"Python SHA256 demo!\n")
(folder / "empty.txt").write_bytes(b"")
print("تم إنشاء أربعة ملفات تجريبية داخل samples/")
شغّل:
python make_demo_files.py
ستحصل على مجلد samples يحوي original.txt وcopy.txt وmodified.txt وempty.txt.
حساب SHA-256 لملف بأبسط كود
أنشئ ملفًا باسم first_hash.py واكتب:
import hashlib
with open("samples/original.txt", "rb") as file:
file_data = file.read()
sha256_hash = hashlib.sha256(file_data).hexdigest()
print(sha256_hash)
الناتج لملفنا التجريبي ثابت ويمكن التحقق منه:
2d393c1b64fa2b0e83017ebf0e59d53aaf0319ba57897cde989bd7ca60f65def
لاحظ استخدام "rb" لقراءة البايتات. لا نستخدم "r" لأن البصمة يجب أن تُحسب من المحتوى الأصلي للملف دون تحويل ترميز النص أو نهايات الأسطر.
{alertWarning}هذا المثال مناسب للملفات الصغيرة فقط: استخدام read() دون تحديد حجم يحمل محتوى الملف كاملًا في الذاكرة. للملفات الكبيرة استخدم الطريقة التالية.حساب البصمة لملفات كبيرة باستخدام chunks
بدل قراءة ملف حجمه عدة جيجابايت دفعة واحدة، نقرأ جزءًا بحجم ميجابايت في كل مرة ونحدّث كائن SHA-256 باستخدام update().
import hashlib
from pathlib import Path
def calculate_sha256(file_path):
hasher = hashlib.sha256()
with Path(file_path).open("rb") as file:
for chunk in iter(lambda: file.read(1024 * 1024), b""):
hasher.update(chunk)
return hasher.hexdigest()
print(calculate_sha256("samples/original.txt"))
تستمر الحلقة إلى أن تعيد read() قيمة فارغة من نوع bytes (b""). وبهذه الطريقة لا نحتاج إلى الاحتفاظ بالملف كله في الذاكرة.
ماذا عن hashlib.file_digest()؟
إذا كنت تستخدم Python 3.11 أو أحدث، تستطيع اختصار حساب بصمة الملف:
import hashlib
with open("samples/original.txt", "rb") as file:
digest = hashlib.file_digest(file, "sha256")
print(digest.hexdigest())
الدالة hashlib.file_digest() أُضيفت في Python 3.11. سنواصل بالمثال القائم على أجزاء القراءة لأنه يعمل أيضًا مع الإصدارات الأقدم.
التحقق من سلامة ملف بعد تحميله
لنفترض أن موقعًا رسميًا يتيح تنزيل ملف ZIP، وبجانبه قيمة SHA-256. بعد التنزيل، احسب بصمة ملفك ثم قارنها بالقيمة الرسمية التي حصلت عليها من مصدر موثوق.
هذا مثال كامل للمقارنة: نستخدم أولًا البصمة الثابتة للملف التجريبي، ثم نحسب بصمة الملف الذي نريد فحصه.
import hashlib
import hmac
from pathlib import Path
expected_hash = "2d393c1b64fa2b0e83017ebf0e59d53aaf0319ba57897cde989bd7ca60f65def"
file_path = Path("samples/original.txt")
with file_path.open("rb") as file:
actual_hash = hashlib.sha256(file.read()).hexdigest()
if hmac.compare_digest(actual_hash, expected_hash):
print("MATCH: الملف مطابق للبصمة المرجعية")
else:
print("MISMATCH: الملف مختلف عن البصمة المرجعية")
هذا مثال بسيط لأن الملف صغير؛ عند التحقق من ملف كبير، استبدل القراءة الكاملة بدالة calculate_sha256() التي شرحناها أعلاه.
{alertWarning}المصدر المرجعي أهم من عملية الحساب نفسها. إذا أخذت الملف والبصمة من مصدر مخترق واحد، قد يكون كلاهما قد تغير. تحتاج بصمة منشورة من جهة موثوقة أو وسيلة تحقق أقوى مثل التوقيع الرقمي عندما يكون إثبات الناشر مطلوبًا.
مقارنة ملفين باستخدام SHA-256
قد تتساءل: هل النسخة التي نسختها إلى USB تطابق الملف الأصلي؟ يمكنك حساب بصمة كل ملف ومقارنتها دون فتح المحتوى يدويًا.
from pathlib import Path
import hashlib
def file_hash(path):
sha = hashlib.sha256()
with Path(path).open("rb") as file:
for chunk in iter(lambda: file.read(1024 * 1024), b""):
sha.update(chunk)
return sha.hexdigest()
first = file_hash("samples/original.txt")
second = file_hash("samples/copy.txt")
print("متطابقان" if first == second else "مختلفان")
عند مقارنة original.txt مع copy.txt ستظهر «متطابقان». أما مع modified.txt فستظهر «مختلفان».
تطابق بصمتَي SHA-256 دليل عملي قوي على تطابق البايتات، لكنه ليس ضمانًا رياضيًا مطلقًا يستحيل معه تصادم البصمات. وإذا اختلفت البصمة، فهذا يعني أن الملفين غير متطابقين؛ ولا تحدد لك البصمة الموضع الذي تغير فيه المحتوى.
مشروع صغير: أداة متكاملة لفحص الملفات
سنحوّل الأمثلة السابقة إلى برنامج باسم sha256_tool.py يستخدم أربعة أوامر: hash لحساب البصمة، وverify للتحقق، وcompare للمقارنة، وreport لإنشاء ملف CSV يحتوي بصمات جميع الملفات الموجودة مباشرة داخل مجلد.
كود sha256_tool.py كاملًا
"""أداة تعليمية لحساب SHA-256 والتحقق من بصمة الملفات."""
import argparse
import csv
import hashlib
import hmac
import re
import sys
from pathlib import Path
CHUNK_SIZE = 1024 * 1024 # ميجابايت واحد لكل قراءة
def calculate_sha256(file_path):
"""حساب SHA-256 دون تحميل الملف كله إلى الذاكرة."""
hasher = hashlib.sha256()
with Path(file_path).open("rb") as file:
for chunk in iter(lambda: file.read(CHUNK_SIZE), b""):
hasher.update(chunk)
return hasher.hexdigest()
def verify_sha256(file_path, expected_hash):
"""التحقق من تطابق الملف مع بصمة مرجعية."""
expected_hash = expected_hash.strip().lower()
if not re.fullmatch(r"[0-9a-f]{64}", expected_hash):
raise ValueError("بصمة SHA-256 يجب أن تتكون من 64 رمزًا سداسيًا.")
actual_hash = calculate_sha256(file_path)
return hmac.compare_digest(actual_hash, expected_hash)
def compare_files(first_path, second_path):
"""مقارنة المحتوى باستخدام بصمتَي SHA-256."""
first_hash = calculate_sha256(first_path)
second_hash = calculate_sha256(second_path)
return hmac.compare_digest(first_hash, second_hash)
def create_report(folder_path, output_path):
"""إنشاء تقرير CSV لبصمة كل ملف في مجلد واحد."""
folder = Path(folder_path)
output = Path(output_path)
if not folder.is_dir():
raise NotADirectoryError(f"المجلد غير موجود: {folder}")
output.parent.mkdir(parents=True, exist_ok=True)
items = sorted(
(p for p in folder.iterdir()
if p.is_file() and p.resolve() != output.resolve()),
key=lambda p: p.name,
)
with output.open("w", newline="", encoding="utf-8-sig") as file:
writer = csv.writer(file)
writer.writerow(["file_name", "sha256"])
for item in items:
writer.writerow([item.name, calculate_sha256(item)])
return len(items)
def main(argv=None):
parser = argparse.ArgumentParser(
description="حساب بصمات SHA-256 والتحقق منها"
)
commands = parser.add_subparsers(dest="command", required=True)
hash_cmd = commands.add_parser("hash", help="حساب بصمة ملف")
hash_cmd.add_argument("file")
verify_cmd = commands.add_parser("verify", help="التحقق من بصمة مرجعية")
verify_cmd.add_argument("file")
verify_cmd.add_argument("expected_hash")
compare_cmd = commands.add_parser("compare", help="مقارنة ملفين")
compare_cmd.add_argument("first_file")
compare_cmd.add_argument("second_file")
report_cmd = commands.add_parser("report", help="تقرير بصمات مجلد")
report_cmd.add_argument("folder")
report_cmd.add_argument("--output", default="sha256-report.csv")
args = parser.parse_args(argv)
try:
if args.command == "hash":
print(calculate_sha256(args.file))
return 0
if args.command == "verify":
if verify_sha256(args.file, args.expected_hash):
print("MATCH: البصمة مطابقة")
return 0
print("MISMATCH: بصمة الملف مختلفة")
return 1
if args.command == "compare":
if compare_files(args.first_file, args.second_file):
print("SAME: بصمتا الملفين متطابقتان")
return 0
print("DIFFERENT: بصمتا الملفين مختلفتان")
return 1
if args.command == "report":
count = create_report(args.folder, args.output)
print(f"تم إنشاء التقرير لعدد {count} ملفًا: {args.output}")
return 0
except (OSError, ValueError) as error:
print(f"خطأ: {error}", file=sys.stderr)
return 2
return 2
if __name__ == "__main__":
raise SystemExit(main())
أضف الكود في ملف مستقل باسم sha256_tool.py داخل مجلد المشروع، ثم استخدم الأوامر التالية.
تشغيل الأداة: حساب بصمة ملف
python sha256_tool.py hash samples/original.txt
الناتج:
2d393c1b64fa2b0e83017ebf0e59d53aaf0319ba57897cde989bd7ca60f65def
تشغيل الأداة: التحقق من checksum
استبدل القيمة التجريبية ببصمة منشورة من مصدر موثوق عندما تستخدم الأداة مع ملف حقيقي.
python sha256_tool.py verify samples/original.txt 2d393c1b64fa2b0e83017ebf0e59d53aaf0319ba57897cde989bd7ca60f65def
الناتج:
MATCH: البصمة مطابقة
إذا غيرت محتوى الملف ستظهر MISMATCH، وتعيد الأداة رمز خروج 1. وإذا أدخلت بصمة غير صالحة أو تعذر فتح الملف تُظهر رسالة خطأ وتعيد رمز خروج 2.
تشغيل الأداة: مقارنة نسختين من ملف
python sha256_tool.py compare samples/original.txt samples/copy.txt
python sha256_tool.py compare samples/original.txt samples/modified.txt
الناتج بالترتيب:
SAME: بصمتا الملفين متطابقتان
DIFFERENT: بصمتا الملفين مختلفتان
إنشاء تقرير SHA-256 لعدة ملفات في CSV
لإنتاج جدول يحوي أسماء الملفات وبصماتها، نفذ:
python sha256_tool.py report samples --output sha256-report.csv
ستحصل على ملف sha256-report.csv خارج مجلد samples، ويمكن فتحه في Excel أو أي محرر نصوص. التقرير يضم الملفات الموجودة مباشرة داخل المجلد، وليس المجلدات الفرعية، ولا يشمل ملف التقرير نفسه إن وضعت الناتج داخل المجلد.
التقرير قائمة بصمات للرجوع إليها، وليس شهادة صحة أو توقيعًا رقميًا. احتفظ بالنسخة المرجعية في مكان موثوق إذا أردت التحقق من الملفات مستقبلًا.
اختبار الأكواد قبل استخدامها
أضفت إلى حزمة المشروع اختبارات باستخدام unittest المدمجة. تختبر البصمة المتوقعة، والتطابق والاختلاف، ورفض القيم غير الصحيحة، وإنشاء تقرير CSV.
python -m unittest discover -s tests -v
لا تحتاج إلى تثبيت pytest أو أي مكتبة إضافية لتنفيذ هذه الاختبارات.
أخطاء شائعة عند حساب SHA-256
| الخطأ | التفسير والحل |
|---|---|
فتح الملف بـ"r" | استخدم "rb" لحساب البصمة من البايتات الأصلية. |
| الملف غير موجود | تحقق من المسار والمجلد الحالي. اقرأ أيضًا حل FileNotFoundError في بايثون. |
| اختلاف البصمة رغم تشابه الاسم | اسم الملف لا يؤثر في SHA-256؛ افحص المحتوى الفعلي والإصدار. |
استخدام read() لملف ضخم | استخدم القراءة المتقطعة chunks بدل تحميل الملف كله. |
| نسخ بصمة خاطئة أو ناقصة | تأكد أنها SHA-256 بطول 64 رمزًا سداسيًا. |
| اعتبار البصمة شهادة أمان | التطابق يؤكد اتساق المحتوى مع القيمة المرجعية، لا خلوه من البرمجيات الخبيثة. |
متى تستخدم SHA-256 ومتى تحتاج شيئًا آخر؟
استخدم SHA-256 عند التحقق من ملف محمّل أو التأكد من تطابق النسخ أو اكتشاف تغيّر محتوى ملف. وإذا كنت تحتاج إثبات أن الملف صدر فعلًا عن جهة معينة، فقد تحتاج توقيعًا رقميًا موثوقًا وليس بصمة منشورة وحدها. ولا تستخدم بصمة ملف وحدها كبديل عن فحص الملفات أمنيًا.
مصادر ومراجع مفيدة
- توثيق Python الرسمي لمكتبة hashlib، بما فيه
file_digest(). - توثيق hmac.compare_digest الرسمي.
- مكتبة أوامر بايثون — فتح الملفات باستخدام with open().
أسئلة شائعة
هل تحتاج SHA-256 إلى تثبيت مكتبة خارجية في بايثون؟
لا. hashlib جزء من مكتبة بايثون القياسية. استخدم import hashlib مباشرة.
هل SHA-256 يقرأ ملفات PDF وZIP والصور؟
نعم. تحسب البصمة من البايتات بصرف النظر عن نوع الملف. لا تحتاج إلى فتح PDF أو فك ضغط ZIP لحساب بصمته.
هل تغيير اسم الملف يغير بصمته؟
لا. إذا لم يتغير محتوى الملف فلن تتغير بصمته لمجرد تغيير اسمه أو امتداده.
هل يمكن مقارنة ملفين كبيرين دون قراءتهما بالكامل إلى الذاكرة؟
نعم. اقرأ البيانات على أجزاء ثابتة باستخدام read(1024 * 1024) وحدث كائن hashlib.sha256() مع كل جزء.
لماذا ظهرت بصمة مختلفة بعد تنزيل الملف؟
ربما تغير المحتوى أو نزل إصدار مختلف، أو استُخدمت بصمة مرجعية لملف آخر. تأكد من تطابق اسم الإصدار وأن القيمة المرجعية مأخوذة من مصدر موثوق.
هل البصمة المتطابقة تعني أن الملف خالٍ من الفيروسات؟
لا. مطابقة SHA-256 لا تعني أن الملف آمن؛ إنها تتحقق من أن المحتوى يطابق القيمة المرجعية التي قارنت بها.



