Skip to content
العودة إلى المدوّنة
دروس تعليمية

أنواع CRC-16: لماذا تختلف نتائج MODBUS و CCITT و XMODEM

نفس البايتات وأربع نتائج CRC-16 مختلفة. تعرّف عبر الإنترنت على كيفية تمييز poly و init و refin و refout و xorout بين MODBUS و CCITT-FALSE و XMODEM.

13 دقيقة قراءة

لماذا تعطي البايتات نفسها أربع نتائج CRC-16 مختلفة

CRC-16 اسم لعائلة لا لخوارزمية، وأفراد هذه العائلة لا يتفقون فيما بينهم. أعطِ البايتات نفسها لـ MODBUS ثم لـ CCITT-FALSE ثم لـ XMODEM، فتعود إليك ثلاثة أعداد بطول 16 بت لا يجمع بينها شيء.

خمسة ثوابت هي التي تحدّد أي فرد من العائلة تشغّله: poly وinit وrefin وrefout وxorout. غيّر واحداً منها يتبدّل الخرج كلّه، ولن يبقى فيه من القيمة القديمة أثرٌ ينبّهك إلى أنك قاربت الصواب. ومعظم أسئلة «لماذا لا يطابق حسابي ما يعطيه الجهاز؟» تنتهي عند هذا الحد بالضبط: الطرفان يشغّلان نوعين مختلفين، ولم يدوّن أحد أيّهما.

كل قيمة في هذا الدليل خرجت من نموذج CRC مُعامَل شُغّل على Python 3.14.7 تحت macOS، وقوبلت بأربع طرق: مقابل zlib.crc32 وbinascii.crc_hqx من المكتبة القياسية، ومقابل قيم التحقق المنشورة في فهرس RevEng CRC، ومقابل وثائق المصنّعين لكل مجموعة معاملات.

1. الإطار نفسه، وأربع نتائج CRC-16

إليك طلب Modbus RTU حقيقياً. الجهاز التابع 01، ورمز الدالة 03 (قراءة سجلات الاحتفاظ)، وعنوان البداية 0x0000، والكمية 0x000A:

01 03 00 00 00 0A

البايتات الستة نفسها عبر أربعة أنواع:

النوعالنتيجةعلى السلك (البايت الأدنى أولاً)
CRC-16/MODBUS0xCDC5C5 CD
CRC-16/IBM-3740 (CCITT-FALSE)0x042828 04
CRC-16/XMODEM0x0A3838 0A
CRC-16/ARC0xD6C5C5 D6

لا رابط بين هذه القيم الأربع. لا نمط مشترك في أنصاف بايتاتها ولا فرق ثابت يفصل بينها، وما من إعادة ترتيب تحوّل إحداها إلى أخرى. أما أن تبدأ اثنتان منها بالبايت نفسه على السلك فمصادفة لا أكثر.

لذلك فإن عبارة «CRC-16» في ورقة بيانات لا تحمل معلومة تُذكر. هي تخبرك أن الخرج بعرض 16 بت، وتقف عند هذا الحد. وإذا أردت النظر إلى نتيجة بأساس آخر أثناء مقارنتها بجهاز يطبع بالثنائي، فإن محوّل أسس الأعداد ينقل قيمة بطول 16 بت بين الست عشري والثنائي دون أن تشغل بالك بالتصفير على اليسار.

2. ماذا يغيّر كلٌّ من المعاملات الخمسة

تحت كل نوع من هذه الأنواع آلة واحدة: سجل إزاحة يبتلع بتاً واحداً في كل مرة، ويُجري XOR مع ثابت كلما سقط واحدٌ من قمّته. والمعاملات هي التي تقرّر ما الذي يدخل السجل وفي أي اتجاه تسير البتات، إضافة إلى عملية XOR أخيرة عند الخروج.

والمحرّك كلّه أربعة عشر سطراً من Python: تنفيذ يعالج بتاً واحداً في كل دورة، بطيء لكنه مقروء، ويعيد إنتاج كل قيمة في هذه المقالة:

def crc(data, width, poly, init, refin, refout, xorout):
    top = 1 << (width - 1)
    mask = (1 << width) - 1
    reg = init
    for byte in data:
        if refin:
            byte = int(f"{byte:08b}"[::-1], 2)
        reg ^= byte << (width - 8)
        for _ in range(8):
            reg = ((reg << 1) ^ poly) if reg & top else (reg << 1)
            reg &= mask
    if refout:
        reg = int(f"{reg:0{width}b}"[::-1], 2)
    return reg ^ xorout

ويمكن مقابلة خرج هذا المحرّك بالمكتبة القياسية في Python. فكلٌّ من zlib.crc32 وbinascii.crc_hqx يعطي إجابة مستقلة، والثلاثة تتطابق:

zlib.crc32(b"123456789")               = 0xCBF43926   engine above = 0xCBF43926   MATCH
binascii.crc_hqx(b"123456789", 0x0000) = 0x31C3       CRC-16/XMODEM = 0x31C3      MATCH
binascii.crc_hqx(b"123456789", 0xFFFF) = 0x29B1       CCITT-FALSE   = 0x29B1      MATCH

poly: معسكران، 0x8005 و0x1021

كثير الحدود هو الثابت الذي يُجرى معه XOR عائداً إلى السجل. ويُكتب في صورته «العادية» مع إضمار البت الأعلى، فيعني 0x8005 المقدار x^16 + x^15 + x^2 + 1، ويعني 0x1021 المقدار x^16 + x^12 + x^5 + 1. وحلقة الإزاحة وXOR ليست إلا قسمة مطوّلة لكثيرات الحدود فوق GF(2): كثير الحدود هو المقسوم عليه والسجل يحمل الباقي الجاري، وكل بت من الرسالة يدفع القسمة خطوة إلى الأمام.

وكل CRC-16 تقريباً تصادفه يستعمل أحد هذين الاثنين. فـ 0x8005 يغطي ARC وMODBUS وUSB، و0x1021 يغطي عقدة CCITT بأكملها، إضافة إلى XMODEM وKERMIT وأنواع RFID. ومعرفة كثير الحدود وحده لا تحدّد النوع أبداً.

init: القيمة التي يبدأ منها السجل

تسيطر هنا قيمتان: 0x0000 و0xFFFF، والفرق بينهما ليس شكلياً. ابدأ من الصفر، فالبايت الصفري يترك السجل عند الصفر، ومن ثم يعطي 00 00 01 و01 قيمتَي CRC متطابقتين، وتجتاز الفحصَ رسالةٌ اكتسبت أصفاراً بادئة أو فقدتها في الطريق. أما البدء من 0xFFFF فيزيل هذه النقطة العمياء، ولهذا يستعمله Modbus وCCITT-FALSE وUSB جميعاً.

refin وrefout: ترتيب البتات لا ترتيب البايتات

يعكس refin البتات الثمانية لكل بايت داخل قبل أن يدخل السجل، ويعكس refout السجل النهائي قبل عملية XOR الأخيرة. والعتاد الذي يُخرج البيانات بادئاً بالبت الأدنى يحصل على هذين الانعكاسين مجاناً، ولهذا تميل الأنواع المنعكسة إلى أن تكون تلك الخارجة من البروتوكولات التسلسلية.

وهذا الزوج من المعاملات هو الأكثر خلطاً بترتيب البايتات (endianness). فهما شيئان مختلفان على مستويين مختلفين، والقسم 6 يفصل بينهما.

xorout: عملية XOR الأخيرة

تأتي هذه العملية بعد refout مباشرة، وقيمتها في CRC-16 عادةً 0x0000 أو 0xFFFF، بينما تستعمل عائلة CRC-32 القيمة 0xFFFFFFFF. وهو أرخص معامل يمكن أن تخطئ فيه، لأن عدم التطابق الناتج عنه يبدو تماماً كعدم التطابق الناتج عن أي معامل آخر.

width: 8 أو 16 أو 32 بت

يحدّد العرض سقف الكشف. فـ CRC بعرض n يلتقط كل خطأ دفقي طوله حتى n بت، ويفوته تلفٌ عشوائي باحتمال يقارب 2^-n. أي مرة واحدة من كل 65,536 مع CRC-16، ومرة واحدة من كل 4.3 مليار مع CRC-32.

وانطلاقاً من CRC-16/XMODEM، مع تغيير معامل واحد بالضبط في كل مرة، مقابل دخل الاختبار القياسي 123456789:

baseline CRC-16/XMODEM                        = 0x31C3
init 0x0000 -> 0xFFFF   (becomes CCITT-FALSE) = 0x29B1
refin/refout -> true    (becomes KERMIT)      = 0x2189
xorout 0x0000 -> 0xFFFF                       = 0xCE3C
poly 0x1021 -> 0x8005                         = 0xFEE8

قلبُ رايةٍ واحدة يعطي خرجاً مختلفاً تماماً. فـ CRC لا يعرف معنى «قريب»: النتيجتان إما أن تتطابقا، وإما ألّا تخبراك بشيء عن مقدار التباعد بين الدخلين، ولذلك فإن التحديق في عدم التطابق لا يقول لك شيئاً عن سببه. والحلقة الداخلية ليست سوى XOR وإزاحات؛ والدليل الكامل للعمليات على البتات يشرح هذه العوامل نفسها، وهذه المقالة تفترضها معروفة.

3. «CCITT» اسم رديء

يُدرج فهرس RevEng CRC واحداً وثلاثين تعريفاً متمايزاً لـ CRC بعرض 16 بت حتى سبتمبر 2026، ثلاثة منها تحمل لافتة CCITT، ولا يتفق أيٌّ من هذه الثلاثة مع الآخر. والصفوف الأربعة أدناه تشترك في كثير الحدود وفي الدخل، ثم تنتهي إلى أربع نتائج لا صلة بينها:

الاسم الشائعاسم الفهرسcheckinitrefin/refoutxorout
CCITT-FALSECRC-16/IBM-37400x29B10xFFFFfalse0x0000
XMODEMCRC-16/XMODEM0x31C30x0000false0x0000
CCITT «الحقيقية»CRC-16/KERMIT0x21890x0000true0x0000
CRC-16/MCRF4XX0x6F910xFFFFtrue0x0000

والتاريخ هنا قصير وغير مفيد. يدرج الفهرس CRC-16/KERMIT تحت الاسمين البديلين CRC-CCITT وCRC-16/CCITT-TRUE، وهذا النوع منعكس. وانتشر كذلك على نطاق واسع تنفيذٌ غير منعكس بقيمة init تساوي 0xFFFF تحت اسم CCITT، ولهذا يسجّل الفهرس «CCITT-FALSE» اسماً بديلاً لـ CRC-16/IBM-3740. أما XMODEM فيقع بينهما: كثير الحدود نفسه، ولا انعكاس، وinit صفر.

فحين تقول ورقة بيانات CCITT، تكون قد أخبرتك أن كثير الحدود هو 0x1021 ولا شيء سوى ذلك. ويبقى أمامك أربعة مرشّحين، والاختيار الخاطئ يعطيك قيمة تبدو معقولة تماماً كالقيمة الصحيحة.

اذكر المعاملات لا الأسماء. فكتابة poly=0x1021, init=0xFFFF, refin=false, refout=false, xorout=0x0000 في وثيقة البروتوكول تشغل سطراً واحداً وتحسم المسألة إلى الأبد، أما كتابة «CRC-16/CCITT» فلا تحسم شيئاً.

4. كيف تعرف أي نوع من CRC-16 بين يديك

يحلّ الفهرس هذه المسألة ببصمة. فكل مدخلة تنشر قيمة تحقق: وهي CRC بايتات ASCII التسعة 123456789. مرّر هذه السلسلة عبر التنفيذ الذي بين يديك، ثم ابحث عن النتيجة في الجدول.

النوعcheckpolyinitrefinrefoutxorout
CRC-16/ARC (IBM/LHA)0xBB3D0x80050x0000truetrue0x0000
CRC-16/MODBUS0x4B370x80050xFFFFtruetrue0x0000
CRC-16/USB0xB4C80x80050xFFFFtruetrue0xFFFF
CRC-16/IBM-3740 (CCITT-FALSE)0x29B10x10210xFFFFfalsefalse0x0000
CRC-16/XMODEM0x31C30x10210x0000falsefalse0x0000
CRC-16/KERMIT (CCITT الحقيقية)0x21890x10210x0000truetrue0x0000
CRC-16/GENIBUS0xD64E0x10210xFFFFfalsefalse0xFFFF
CRC-16/MCRF4XX0x6F910x10210xFFFFtruetrue0x0000
CRC-32/ISO-HDLC (zip, PNG, zlib)0xCBF439260x04C11DB70xFFFFFFFFtruetrue0xFFFFFFFF
CRC-32/BZIP20xFC8919180x04C11DB70xFFFFFFFFfalsefalse0xFFFFFFFF
CRC-32C (Castagnoli, iSCSI)0xE30692830x1EDC6F410xFFFFFFFFtruetrue0xFFFFFFFF
CRC-8/SMBUS0xF40x070x00falsefalse0x00
CRC-8/MAXIM-DOW (1-Wire)0xA10x310x00truetrue0x00

وثمة تفصيل واحد يُغرق كثيراً من هذه المقارنات: 123456789 تعني البايتات التسعة 31 32 33 34 35 36 37 38 39، لا العدد 123456789 ولا سلسلة منتهية بمحرف فارغ. فإن سلّمت لغتُك الدالةَ سلسلةً عريضة أو ألحقت بها محرف إنهاء، فأنت تحسب على دخل مختلف، وسيُخطئ كل صف في الجدول. وتضيف الحمولات غير ASCII فخاً ثانياً: النص نفسه مُرمَّزاً بـ UTF-8 ومُرمَّزاً بـ Windows-1252 متتاليتان مختلفتان من البايتات، ومن ثمّ قيمتان مختلفتان لـ CRC. وجدول ASCII والمحوّل يعرض البايت الكامن خلف كل محرف، فترى بعينك أي المتتاليتين بين يديك.

وإن لم تطابق قيمة التحقق أي صف على الإطلاق، فاستبعد الأسباب المملّة قبل أن تشكّ في كثير حدود مخصّص:

  1. أنت تقرأ النتيجة معكوسة من على السلك، والفرق كله في ترتيب البايتات.
  2. المرسِل يُدخل في الحساب بايت عنوان أو بايت طول أو حقل CRC نفسه، أو يستثنيه.
  3. للمصنّع قيمة init خاصة به ليست 0x0000 ولا 0xFFFF، وهذا يحدث فعلاً في بروتوكولات العدّادات المملوكة، وعادةً ما يكون مدفوناً في حاشية.

تعداد المعاملات بالقوة الغاشمة

حين يرفض المصنّع أن يخبرك ولا تستطيع قراءة برنامجه الثابت، اطلب منه شيئاً واحداً بدل ذلك: إطاراً ملتقطاً مع قيمة CRC التي أنتجها جهازه له. ثم عدِّد الاحتمالات. كثيرا حدود، وقيمتان لـ init، وrefin وrefout كلٌّ على حدة، وقيمتان لـ xorout: المجموع 32 تركيبة فقط:

target = 0x4B37                    # القيمة التي أعادها تنفيذهم
for poly in (0x8005, 0x1021):
    for init in (0x0000, 0xFFFF):
        for refin in (False, True):
            for refout in (False, True):
                for xorout in (0x0000, 0xFFFF):
                    if crc(b"123456789", 16, poly, init, refin, refout, xorout) == target:
                        print(f"poly=0x{poly:04X} init=0x{init:04X} "
                              f"refin={refin} refout={refout} xorout=0x{xorout:04X}")
poly=0x8005 init=0xFFFF refin=True refout=True xorout=0x0000

إصابة واحدة، وهي CRC-16/MODBUS. أعطِ الشفرةَ إطاراً حقيقياً بدل 123456789 وسيعمل المسح نفسه بتركيباته الاثنتين والثلاثين على أي رسالة لديك لها قيمة CRC معلومة الصحة. وإن نجت عدة تركيبات من عيّنة واحدة، فشغّل إطاراً ثانياً وخذ تقاطع النتائج.

5. Modbus RTU في الممارسة العملية

يستعمل Modbus RTU النوعَ CRC-16/MODBUS: كثير الحدود 0x8005، وinit تساوي 0xFFFF، وانعكاس عند الدخل والخرج، ولا عملية XOR نهائية. وقيمة CRC لطلبنا ذي البايتات الستة هي 0xCDC5، والإطار المكتمل هو:

01 03 00 00 00 0A C5 CD

ترتيب السلك معكوسٌ عن الطريقة التي تكتب بها القيمة

القيمة هي 0xCDC5، والبايتان المُلحقان بالإطار هما C5 CD. فمواصفة Modbus تنصّ على وضع بايت CRC الأدنى أولاً، وهو عكس الترتيب الذي تقرأ به العدد الست عشري، وهو ما يوقع الناس باستمرار. وكل حقل آخر متعدّد البايتات في الإطار نفسه، بما فيه عدّاد السجلات 00 0A، يسير بالبايت الأعلى أولاً. وCRC وحده هو الاستثناء.

ولهذا أثر جانبي مفيد. احسب CRC-16/MODBUS على الإطار كله، بما فيه بايتا CRC، فتكون النتيجة:

0x0000

هذا هو روتين التحقق كلّه عند المستقبِل. لا داعي لفصل البايتين الأخيرين وتبديل ترتيبهما ومقارنتهما بقيمة محسوبة محلياً؛ شغّل CRC على كل ما وصل وتحقّق من أن الناتج صفر. وكلما قلّت الخطوات قلّت المواضع التي قد تعكس فيها البايتات خطأً.

لماذا تُظهر وثائق Modbus القيمة 0xA001

افتح أي تنفيذ لـ Modbus تقريباً تجد الثابت في الشفرة 0xA001 لا 0x8005. وكلاهما صحيح. فـ 0xA001 هو 0x8005 وقد عُكست بتاته الست عشرة، وهو يخصّ الصورة المنعكسة من الخوارزمية، حيث يزيح السجل يميناً بدل اليسار ولا تحتاج بايتات الدخل إلى عكس فردي. والتنفيذان ينتجان الخرج نفسه تماماً، والاختلاف في الداخل وحده. ووجود 0xA001 في الشفرة علامة موثوقة على تنفيذ منعكس لـ CRC-16.

وثمة أمر ينبغي الانتباه له في أي التقاط. فـ Modbus ASCII تأطير منفصل يحمل كل بايت في صورة محرفين ست عشريين بين 3A (:) في البداية و0D 0A في النهاية، ويستعمل LRC لا CRC. فإن أظهر أثرُك 3A 30 31 30 33 حيث كنت تتوقع 01 03، فأنت في وضع ASCII ولن يطابق أي نوع من CRC أبداً. وجدول ASCII يردّ تلك البايتات مباشرة إلى محارفها.

الخوارزمية نفسها بلغة C

نادراً جداً ما تستعمل البرامج الثابتة في Modbus نموذجَ Python أعلاه، بل تستعمل الصورة المنعكسة مباشرة: إزاحة يميناً وXOR مع 0xA001:

uint16_t crc16_modbus(const uint8_t *buf, size_t len) {
    uint16_t crc = 0xFFFF;
    for (size_t i = 0; i < len; i++) {
        crc ^= buf[i];
        for (int b = 0; b < 8; b++)
            crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
    }
    return crc;
}

وحين تُطعَم الإطار 01 03 00 00 00 0A، تعيد هذه الدالة 0xCDC5، وهي القيمة نفسها التي أنتجها محرّك Python. وتعطي سلسلةُ التحقق 123456789 القيمةَ 0x4B37. وقد شُغّل الاثنان تحت Apple clang 21، ويطابقان جدول القسم 4 بتاً ببت.

6. refin/refout ليسا ترتيب البايتات

والخلط بينهما مكلف لأن Modbus يجمع بين المستويين في الإطار الواحد.

ترتيب البايتات (endianness) يتعلق بالبايتات داخل قيمة متعددة البايتات: هل تُخزَّن 0xCDC5 أو تُرسَل في صورة CD C5 أم C5 CD؟ ولا يتحرك شيء داخل البايت الواحد. وهذا هو المستوى الذي يغطيه بالكامل النظام الكبير مقابل النظام الصغير في ترتيب البايتات، ولن تكرّره هذه المقالة.

أما الانعكاس فيتعلق بالبتات داخل البايت الواحد. فـ refin يعكس البتات الثمانية لكل بايت قبل أن يراه السجل: البت 0 يصير البت 7، والبايت يحتفظ بموضعه في الرسالة. ولا أثر لأي من الإعدادين على الآخر.

وفي إطار Modbus يحدث الأمران معاً، وكلٌّ منهما مستقل عن الآخر:

  • داخل الخوارزمية، تكون قيمة refin وrefout هي true، فتُعكس البتات داخل البايتات.
  • وعلى السلك، يُلحَق CRC النهائي بالبايت الأدنى أولاً، وهذا قرار في ترتيب البايتات اتخذه البروتوكول.

وإطفاء الانعكاس لـ«إصلاح» مشكلة في ترتيب البايتات ينتج نوعاً مختلفاً بالكامل، وتبديل بايتَي نهاية الإطار للتعويض عن خطأ في الانعكاس ينتج قيمة خاطئة على نحو جديد. شخّص واحدة في كل مرة: اضبط قيمة التحقق بالخوارزمية وحدها أولاً، ثم عالج تخطيط الإطار.

7. CRC-32 مقابل CRC-16: أيهما ينبغي أن تستعمل؟

لدى CRC-32 المشكلة نفسها التي لدى CRC-16، لكنها أقل ظهوراً، لأن نوعاً واحداً يسيطر سيطرة تامة حتى إن معظم المطورين لا يعرفون أصلاً بوجود غيره.

النوعcheckpolyrefin/refout
CRC-32/ISO-HDLC0xCBF439260x04C11DB7true
CRC-32/BZIP20xFC8919180x04C11DB7false
CRC-32C (Castagnoli)0xE30692830x1EDC6F41true

وISO-HDLC هو ما يحسبه zlib.crc32، وتصادفه في كل موضع تُنقل فيه البيانات أو تُخزَّن. فملفات Zip تخزّنه لكل مدخلة في الدليل المركزي، وكل كتلة PNG تنتهي بواحدة منه تغطي نوع الكتلة وبياناتها. وتسلسل فحص الإطار في Ethernet يستعمل مجموعة المعاملات نفسها في نهاية كل إطار يرسله خادمك. أما BZIP2 فهو كثير الحدود ذاته مع إطفاء الانعكاسات، وهذا ينتج قيمة لا تشترك في شيء مع ISO-HDLC.

ويظهر CRC-16 في الجوار نفسه كذلك: فعنقود Redis يوجّه المفتاح إلى واحدة من خاناته البالغة 16,384 عبر CRC16(key) mod 16384، ولهذا تهبط المفاتيح التي تحمل وسم التجزئة نفسه على العقدة نفسها.

لماذا ربح CRC-32C يانصيب العتاد

يستعمل CRC-32C كثير حدود مختلفاً يكشف الأخطاء بصورة أفضل على الكتل القصيرة التي ترسلها بروتوكولات التخزين والشبكات. وهذا ما كسبه iSCSI، ومجاميع تحقق البيانات الوصفية في ext4، وSCTP، وBtrfs. ثم وضعته Intel في السيليكون: فتعليمة crc32 في SSE4.2 تحسب CRC-32C مباشرة، وهو ما يحوّله إلى فحص سلامة مجاني عملياً على معماريات x86 الحديثة. وإن كنت تختار اليوم CRC لشفرة جديدة وليس عليك قيد توافق، فهذا هو الذي تختاره.

وليس أيٌّ من هذه بصمةً تتحقق بها من ملف نزّلته. فحين تريد مجموع تحقق يؤكد أن ملفاً وصل سليماً، يكون مولّد بصمة MD5 هو الأداة المألوفة، ويشرح MD5 مقابل SHA-256 أي خلاصة تثق بها ولأي غرض. فـ CRC يجيب عن سؤال «هل تلف هذا؟»، أما البصمة المعمّاة فتجيب عن سؤال «هل هذا هو المحتوى الذي أتوقعه بالضبط؟».

8. CRC لا يوقف العبث

رسالتان بصيغة JSON، معناهما متناقض وقيمة CRC-32 لهما واحدة:

message A = b'{"to":"alice","amount":100}  H\xf6\xc0E'
message B = b'{"to":"mallory","amount":999}\xb2\xb2\xc2\xc2'
CRC-32(A) = 0x12345678
CRC-32(B) = 0x12345678

وSHA-256 على الرسالتين نفسيهما:

A -> a83b90f5e3f21dd32039e0e693a8b15864eda83e258c115deeee50a60c09ae23
B -> f9319ee0507d719c7260535dff33df721ddd9c0e1857690f3d8e02f92601b742

هذه البايتات الأخيرة لم تأتِ من تخمين غاشم، بل من حلّ جبري مباشر. CRC دالة خطية، وإلحاق أربعة بايتات برسالة يُسقَط على خرج CRC-32 إسقاطاً تقابلياً، فلأي رسالة ولأي قيمة هدف توجد لاحقة واحدة بالضبط بطول أربعة بايتات توصلك إليها، وإيجادها مسألة حسابية.

وبإلحاق البايتات الأربعة المحلولة 46 C9 6E 0B بالنص hello world:

CRC-32(b'hello world' + 46 C9 6E 0B) = 0xDEADBEEF   target 0xDEADBEEF   MATCH

فالمهاجم القادر على تعديل حمولتك قادر إذن على تصحيح قيمة CRC أيضاً، في زمن ثابت لا يبحث فيه عن شيء. ووضع سرّ في مقدمة الرسالة لا ينقذها: ففي تعديل يحافظ على الطول، يتوقف التصحيح المطبّق على CRC على البتات التي تغيّرت وحدها، ومن ثمّ يمكن حسابه دون معرفة السر إطلاقاً. وهذا هو شكل الهجوم الذي كسر WEP.

يحمي CRC من ضجيج الإرسال، وهو ضجيج عشوائي بلا غاية. أما الخصم فيختار ما يغيّره ويعرف لماذا يغيّره، وأمام هذا لا يقدّم CRC شيئاً. وحين يكون المطلوب هو الأصالة لا مجرد السلامة، فأنت بحاجة إلى بنية بمفتاح: مولّد HMAC ينتج واحدة على أي حمولة وأي مفتاح، ويمرّ دليل فشل التحقق من توقيع Webhook على المواضع التي تُعثِر الناس حين يوصّلونها بنقطة نهاية حقيقية.

9. الأسئلة الشائعة

هل CRC-16/CCITT هو نفسه CRC-16/CCITT-FALSE؟

لا، CRC-16/CCITT-FALSE وCRC-16/CCITT نوعان مختلفان. فـ CCITT-FALSE هو CRC-16/IBM-3740: قيمة init تساوي 0xFFFF، بلا انعكاس، وقيمة تحقق 0x29B1. أما النوع الذي يُقصد عادةً بـ CCITT المجرّدة فهو CRC-16/KERMIT: قيمة init تساوي 0x0000، مع انعكاس، وقيمة تحقق 0x2189. وهما يشتركان في كثير الحدود 0x1021 ولا يتفقان على شيء سواه.

لماذا تختلف حاسبة عبر الإنترنت مع جهازي؟

اختلاف المعاملات، في جميع الحالات تقريباً. فالأداة تفترض نوعاً والجهاز ينفّذ نوعاً آخر. مرّر بايتات ASCII 123456789 عبر الاثنين، وقارن قيمتَي التحقق بالجدول الوارد في القسم 4، وعادةً ما يكشف المعاملُ غير المتطابق عن نفسه في أقل من دقيقة.

لماذا يضع Modbus بايت CRC الأدنى أولاً؟

لأن المواصفة تنصّ على ذلك، ولأنه الحقل الوحيد في الإطار الذي يسلك هذا المسلك. فعناوين السجلات وأعدادها تسير بالبايت الأعلى أولاً، أما CRC في نهاية الإطار فلا. احسب CRC-16/MODBUS على الإطار كله بما فيه هذان البايتان وتحقّق من 0x0000، بدل أن تعيد ترتيبهما بنفسك.

ما الذي يعكسه refin وrefout بالضبط؟

بتات داخل البايت، ولا بايتات داخل الرسالة أبداً. فـ refin يعكس البتات الثمانية لكل بايت داخل قبل المعالجة، وrefout يعكس السجل النهائي قبل عملية XOR الأخيرة. وكلاهما مستقل عن ترتيب البايتات (endianness) الذي يحكم كيفية تخطيط قيمة متعددة البايتات على السلك.

هل أستعمل CRC-16 أم CRC-32؟

يفوت CRC-16 تلفاً عشوائياً مرة واحدة تقريباً من كل 65,536 مرة، ويفوته CRC-32 مرة من كل 4.3 مليار. وفي الإطارات التسلسلية القصيرة التي لا تتعدى بضع عشرات من البايتات، يفي CRC-16 بالغرض، وغالباً ما يفرضه البروتوكول أصلاً. أما للملفات وأطر الشبكة وأي شيء يزيد على بضعة كيلوبايتات، فاستعمل CRC-32.

هل أستطيع استعمال CRC توقيعاً لواجهة API؟

لا يصلح CRC توقيعاً لواجهة API بأي حال. فـ CRC خطي، ومن ثمّ يستطيع كل من يغيّر الحمولة أن يعيد حساب قيمة مطابقة، كما أن إلحاق أربعة بايتات مختارة يبلغ أي هدف تسمّيه في CRC-32. والتواقيع تحتاج إلى مفتاح سري وبنية غير خطية. استعمل HMAC مع SHA-256.

هل يمكن استرجاع البيانات الأصلية من قيمة CRC؟

لا سبيل إلى استرجاع البيانات الأصلية من قيمة CRC. فـ CRC-16 يضغط أي دخل إلى 16 بت، ومن ثمّ يشترك عدد لا نهائي من الرسائل في كل قيمة. غير أن الاتجاه المعاكس يبقى مفيداً للمهاجمين: فمع قيمة هدف معطاة يمكنك بناء رسالة تنتجها، وهذا بالضبط سبب أن CRC لا يوثّق شيئاً.

وثيقة المصنّع تقول «CRC-16» فقط. كيف أحدّد المعاملات؟

اطلب إطاراً ملتقطاً واحداً مع قيمة CRC التي حسبها جهازهم له، ثم شغّل مسح التركيبات الاثنتين والثلاثين الوارد في القسم 4 على هذا الزوج. وإن نجت أكثر من مجموعة معاملات واحدة، فكرّر مع إطار ثانٍ واحتفظ بالتقاطع. وعيّنتان تكفيان غالباً.

الوسوم: crc checksum modbus embedded data-integrity

مقالات ذات صلة

عرض جميع المقالات