การ ตรวจสอบความซ้ำซ้อนแบบวนซ้ำ (CRC) รหัสตรวจจับข้อผิดพลาด

ตรวจสอบความซ้ำซ้อนแบบวนซ้ำ CRC เป็นอัลกอริทึมการตรวจจับข้อผิดพลาดทั่วไปสำหรับการส่งข้อมูล ไม่เคยออกแบบมาสำหรับการเข้ารหัส อย่างไรก็ตาม บางครั้งก็ใช้ในโปรโตคอล เช่น WEP เมื่อใช้เพื่อจุดประสงค์ในการเข้ารหัส อาจนำไปสู่การประนีประนอมกับความปลอดภัยของการแลกเปลี่ยน ในบทความนี้จะมีรายละเอียดเกี่ยวกับการโจมตีที่เป็นไปได้ต่อการใช้ CRC ในการเข้ารหัส

รหัสทั้งหมดที่ใช้ในบทความนี้ (และอื่น ๆ ) สามารถพบได้ในGitHub ของฉัน หรือที่นี่

ซีอาร์ซีคืออะไร

การตรวจสอบความซ้ำซ้อนแบบวนซ้ำ (CRC) เป็นรหัสตรวจจับข้อผิดพลาดตามโมดูโลการหารพหุนามสอง CRC ใช้สำหรับตรวจหาข้อผิดพลาดในการส่งข้อมูล เช่น ในการส่งข้อมูลผ่านบลูทูธ แต่ไม่ควรใช้เพื่อวัตถุประสงค์ในการเข้ารหัส

CRC (มีโปรโตคอลขึ้นอยู่กับคำจำกัดความของ CRC มากมาย) ถูกกำหนดโดย:

  • พหุนามP (ซึ่งก็คือx8+x7+x6+x4+x2+ 1�8+�7+�6+�4+�2+1กรณีเป็น CRC-8-Bluetooth)
  • การดำเนินการก่อนและหลังการดำเนินการหารด้วยP

การคำนวณ CRC จะทำในพื้นที่พหุนาม ดังนั้นจึงถือว่าข้อมูลเป็นพหุนาม

ตัวอย่างเช่น,” ก” = 97 = 0 ข01100001 =x6+x5+ 1″ก”=97=0ข01100001=�6+�5+1.

เพื่อสรุปชุดของบิตb ( n ) b ( n − 1 ) . . . ข( 1 ) ข( 0 )ข(น)ข(น-1)…ข(1)ข(0)สามารถเทียบได้กับพหุนามข= ข( น)เอ็กซ์น+ ข( n − 1 )เอ็กซ์(n − 1 ) + _ . . + ข( 1 ) X+ ข( 0 )ข=ข(น)เอ็กซ์น+ข(น-1)เอ็กซ์(น-1)+…+ข(1)เอ็กซ์+ข(0)

และ CRC จะคำนวณเป็น:

  x = (op_start(B)) mod[P]
  CRC(B) = op_end(x)

ด้วยop_startและop_endสองการดำเนินการกับพหุนาม

การแสดงพหุนาม

ในเอกสารประกอบ CRC จะใช้การแทนชื่อพหุนามที่แตกต่างกัน การแสดงแบบปกติ การกลับกัน การกลับกัน และการกลับกัน การเป็นตัวแทนที่ใช้ขึ้นอยู่กับข้อกำหนด และจะส่งผลต่ออัลกอริทึมที่ใช้ในการคำนวณ CRC แต่จะไม่แก้ไขผลลัพธ์ รายละเอียดในการเป็นตัวแทนมีดังนี้

สำหรับ CRC8 ที่ใช้พหุนามx8+x7+x6+x4+x2+ 1�8+�7+�6+�4+�2+1การแสดงที่แตกต่างกันมีดังนี้:

พิมพ์เทียบเท่ากับค่าเลขฐานสิบหก
ปกติx8+ (x7+x6+x4+x2+ 1 )�8+(�7+�6+�4+�2+1)0xD5
กลับด้าน1 + (x2+x4+x4+x6+x7+x8)1+(�2+�4+�4+�6+�7+�8)0xAB
ซึ่งกันและกัน( 1 +x2+x4+x4+x6+x7) +x8(1+�2+�4+�4+�6+�7)+�80x57
กลับกัน(x8+x7+x6+x4+x2) + 1(�8+�7+�6+�4+�2)+10xEA

รายชื่อพหุนามสำหรับ CRCs ต่างๆ สามารถพบได้ที่นี่ ตรวจสอบความซ้ำซ้อนแบบวนซ้ำ

การคำนวณ CRC

ในส่วนนี้ฉันนำเสนอวิธีคำนวณใน python CRC ด้วยพหุนามของความยาวคูณด้วย 8 รหัสได้รับแรงบันดาลใจจากที่นำเสนอในRosettaCode

class CRC:
    '''
    This CRC class works to generate crc of length multiple of 8.
    :param polynome int: polynome in reciprocal form
    :param init_xor int: first data xoring to apply on the data
    :param end_xor int: final data xoring to apply on the data
    '''
    def __init__(self, polynome, init_xor, end_xor):
        self.polynome = polynome
        self.crc_len = (len(hex(polynome)[2:])-1)//2
        self.init_xor = init_xor
        self.end_xor = end_xor
        self.crc_table = self.create_table()
        self.inv_crc_table = self.create_inv_table()

    def create_table(self):
        table = []
        for i in range(256): # for all byte value
            k = i
            for j in range(8): # compute modulus
                if k & 1:
                    k ^= self.polynome
                k >>= 1
            table.append(k)
        return table

    def crc(self, bytestring, crc_val=0):
        crc_val ^= self.init_xor
        for byte in bytestring:
            # Compute crc recursively
            crc_val = (crc_val >> 8) ^ self.crc_table[(crc_val & 0xff) ^ byte]
        return crc_val ^ self.end_xor

ในการคำนวณ CRC32 แบบคลาสสิก พารามิเตอร์จะเป็นดังนี้:

polynome =  0x1db710640
init_xor = int('FF'*4, 16)
end_xor = int('FF'*4, 16)

คุณสมบัติบางอย่างของ CRC

CRCs มีคุณสมบัติที่น่าสนใจซึ่งจะใช้ในภายหลัง อย่างไรก็ตาม การสาธิตคุณสมบัตินี้ปล่อยให้ผู้อ่านสนใจ

  • CRC(A ^ B ^ C) = CRC(A) ^ CRC(B) ^ CRC(C), for len(A) = len(B) = len(C)
  • สามารถคำนวณ CRC แบบเรียกซ้ำได้ (ดูรหัสในส่วนก่อนหน้า):
    • CRC(0) = c0
    • CRC(i+1) = (CRC(i) >> 8) ^ T[(CRC(i) & 0xFF) ^ M[i]]ด้วย T ตารางค่าคงที่ (ดูcreate_table)
  • การเกิดซ้ำของ CRC เพิ่มคุณสมบัติบางอย่าง:
    • CRC('', val) = val
    • CRC(M, val) = CRC(M[1:], CRC(M[0], val))
    • CRC(M) = CRC(M, 0)

การกลับ CRC

CRC ไม่มีจุดประสงค์ในการเข้ารหัส และเป็นเรื่องง่ายที่จะค้นหาข้อมูลสองชุดที่มี CRC เหมือนกัน ในส่วนนี้จะอธิบายวิธีการต่างๆ ในการสร้างข้อมูลที่แตกต่างกันด้วย CRC เดียวกัน

แนวทางกำลังด้อย

วิธีแรกคือการใช้กำลังดุร้าย สำหรับ CRC ที่มีความยาวlและข้อความM=M[0]M[1]…M[n-1]จำนวนnบิต จะพบข้อความอื่นที่มี CRC เดียวกันได้โดยการทดสอบชุดค่าที่เป็นไปได้ทั้งหมดสำหรับ M[nl ]..M[n-1] .

วิธีนี้ใช้ง่ายแต่ช้ามาก หากเวลาในการคำนวณเป็นที่ยอมรับสำหรับ CRC32 จะกลายเป็นเรื่องที่น่ารำคาญมากสำหรับ CRC64 หรือเมื่อคุณจำเป็นต้องคำนวณหลายรายการพร้อมกัน

วิธีการที่ไม่ไร้เดียงสา

เป็นไปได้ที่จะทำได้ดีกว่าวิธีการไร้เดียงสา ในการทำเช่นนั้น อัลกอริทึมการคำนวณ CRC จำเป็นต้องย้อนกลับ

เพื่อลดความซับซ้อน เนื่องจาก CRC ของ 32 บิตจะได้รับการพิจารณา แต่สามารถใช้วิธีการเดียวกันนี้กับความยาวเท่าใดก็ได้

ตามที่เห็นก่อนหน้านี้ CRC สามารถคำนวณซ้ำได้ด้วยเงื่อนไขนี้ * CRC(0) = c0 *CRC(i+1) = (CRC(i) >> 8) ^ T[(CRC(i) & 0xFF) ^ M[i]]

ในกรณีของการแทนสี่ไบต์:

องค์ประกอบบิตข0ข0ข1ข1ข2ข2ข3ข3
(CRC(i) >> 8) = Bข0ข0ข1ข1ข2ข2
T[(CRC(i) & 0xFF) ^ M[i]] = Tที0ที0ที1ที1ที2ที2ที3ที3
CRC(i+1) = กก0ก0ก1ก1ก2ก2ก3ก3
ซีอาร์ซี(i+1)ที0ที0ที1ที1^ข0ข0ที2x โออาร์ข1ที2�โอรข1ที3x โออาร์ข2ที3�โอรข2

สิ่งแรกที่ต้องตระหนักคือถ้าเรารู้ว่าCRC(i+1)เรารู้ 8 บิตทางด้านซ้ายของT[(CRC(i) & 0xFF) ^ M[i]].

ค่า T ทั้งหมดมีค่าเฉพาะสำหรับ 8 บิตทางด้านซ้าย ( T[i] >> (crc_len - 8หรือที0ที0บนโต๊ะ). T_invสามารถสร้างตารางย้อนกลับ ได้

มีคุณสมบัติดังต่อไปนี้ * T[T_inv[i]] = i *T_inv[T[i] >> (crc_len - 8)] = i

จากนั้นค่าของ CRC(i) ยกเว้น 8 บิตแรกทางขวา (ข0ข1ข2ข0ข1ข2) สามารถทราบได้โดย:

(CRC(i) >> 8) = CRC(i+1) ^ T[T_inv[CRC(i+1) >> (crc_len - 8)]]

การดำเนินการนี้สามารถทำได้CRC(i)ทั้งหมด

ตอนนี้ต้องหาค่า M

แต่เรายังรู้ค่ากลางอีกค่าหนึ่งด้วย (เช่น CRC(0) ซึ่งเป็น 0 หรือ -1 (0xFF..FF) หรืออาจเป็น CRC ของบิต N-4) จากนั้นเราก็สามารถคำนวณค่า M[i] ได้ เนื่องจากเราทราบ CRC และ T Recursively ที่สอดคล้องกัน เราจึงสามารถสร้างค่าข้อความใหม่ได้จากจุดนี้

สำหรับกรณีของ CRC32:

CRC(i+1) = T[(CRC(i) & 0xFF) ^ M[i]] ^
           (
            (T[(CRC(i-1) & 0xFF) ^ M[i-1]] ^
             (
              (T[(CRC(i-2) & 0xFF) ^ M[i-2]] ^
               (
                (T[(CRC(i-3) & 0xFF) ^ M[i-3]] ^
                 (
                  (T[(CRC(i-4) & 0xFF) ^ M[i-3]] ^
                  (CRC(i-4) >> 8)
                  ) >> 8
                 )
                ) >> 8
               )
              ) >> 8
             )
            ) >> 8
           )
         = T[(CRC(i) & 0xFF) ^ M[i]] ^
           (T[(CRC(i-1) & 0xFF) ^ M[i-1]] >> 8) ^
           (T[(CRC(i-2) & 0xFF) ^ M[i-2]] >> 16) ^
           (T[(CRC(i-3) & 0xFF) ^ M[i-3]] >> 24) ^
           (T[(CRC(i-4) & 0xFF) ^ M[i-3]] >> 32) ^
           (CRC(i-4) >> 40)
        = T[(CRC(i) & 0xFF) ^ M[i]] ^
           (T[(CRC(i-1) & 0xFF) ^ M[i-1]] >> 8) ^
           (T[(CRC(i-2) & 0xFF) ^ M[i-2]] >> 16) ^
           (T[(CRC(i-3) & 0xFF) ^ M[i-3]] >> 24) ^
           (T[(CRC(i-4) & 0xFF) ^ M[i-3]] >> 32)

และเรายังสามารถพิสูจน์ CRC ของความยาวได้อย่างง่ายดายn*8

CRC(i+1) = T[(CRC(i) & 0xFF) ^ M[i]] ^
           (T[(CRC(i-1) & 0xFF) ^ M[i-1]] >> 8) ^
           (T[(CRC(i-2) & 0xFF) ^ M[i-2]] >> 16) ^
           ...
           (T[(CRC(i-(n-1)) & 0xFF) ^ M[i-(n-1)]] >> (n*8))

ค่า CRCs ที่ขั้นตอนi+1ขึ้นอยู่กับค่า CRC เริ่มต้นที่ขั้นตอนi-(n-1) เท่านั้น (ซึ่งเรียกว่าเป็น crc ของส่วนของข้อความM[i-(n-1): i+1]ที่จะไม่ถูกแก้ไข)

ในการแก้ไขข้อความให้มี CRC เฉพาะ ฉันเขียนโค้ดต่อไปนี้:

    def create_inv_table(self):
        inv_table = [0] * 256
        l = self.crc_len*8 - 8
        for i, elem in enumerate(self.crc_table):
            val = elem >> l
            inv_table[val] = i
        return inv_table

    def crc_inv(self, data, crc_target):
        original_crc = self.crc(data, 0)
        prev_crc = crc_target ^ self.end_xor
        index_table = []
        crc_list = [prev_crc]
        new_data = []
        l = self.crc_len*8 - 8
        for i in range(self.crc_len):
            crc_head = prev_crc >> l
            index = self.inv_crc_table[crc_head]
            index_table = [index] + index_table
            prev_crc = (prev_crc ^ self.crc_table[index]) << 8
            crc_list = [prev_crc] + crc_list
        crc_list = [original_crc ^ self.init_xor] + crc_list
        for i in range(self.crc_len):
            new_data.append((crc_list[i] ^ index_table[i]) & 0xFF)
            crc_list[i+1] = (crc_list[i] >> 8) ^ (self.crc_table[index_table[i]])
        return data + bytes(new_data)

โดยใช้รหัส:

>>> import crc
>>> crc = crc.CRC(0x1db710640, int('FF'*4, 16), int('FF'*4, 16))
>>> crc.crc_inv(b'', 0xDEADBEEF)
b'\xc3\xd8$\x06'
>>>> hex(crc.crc(b'\xc3\xd8$\x06'))
'0xdeadbeef'

CRC และการเข้ารหัส

แม้ว่า CRC จะไม่เคยถูกสร้างขึ้นเพื่อวัตถุประสงค์ในการเข้ารหัส แต่บางครั้งก็ถูกใช้ในการเข้ารหัสแบบกำหนดเอง (ซึ่งไม่ควรทำ) ฉันเห็นไม่กี่ครั้งในระบบฝังตัวที่ฉันตรวจสอบ ในส่วนนี้ฉันจะอธิบายผ่านตัวอย่างหนึ่งว่าทำไมไม่ควรใช้ในระบบ crypto

รหัสสตรีม

การเข้ารหัสของสตรีมทำงานในลักษณะที่คล้ายกับแป้นแบบใช้ครั้งเดียว แป้นแบบใช้ครั้งเดียวถูกสร้างขึ้นผ่านอัลกอริทึมการเข้ารหัส และคีย์นี้จะถูก xored ลงในข้อมูลเพื่อเข้ารหัส ตรวจสอบความซ้ำซ้อนแบบวนซ้ำ

กุญแจแบบนั้น:Key = E(IV, K)|E(IV, K+1)|....|E(IV, K+N)|...

และ XOR คีย์นี้พร้อมข้อความ:C = E(M, K1|K2|...|KN) = E(M, K1)|E(C, K2)...|E(C, KN)

ซึ่งสามารถสังเกตได้ว่าC = E(M, K) = M ^ K

ไม่ควรใช้ CRC เพื่อความสมบูรณ์

ตอนนี้ เรามาดูตัวอย่างหนึ่งว่าทำไมจึงไม่ควรใช้ CRC สำหรับการตรวจสอบความสมบูรณ์ในบริบทการเข้ารหัสลับ ในกรณีของการใช้ CRC เป็นอัลกอริทึมความสมบูรณ์เมื่อใช้การเข้ารหัสสตรีม เป็นไปได้ที่จะแก้ไขข้อความที่เข้ารหัสโดยไม่ต้องแก้ไข CRC ดังนั้นจึงไม่ถูกตรวจพบ

จากตอนที่แล้วCRC(A ^ B ^ C) = CRC(A) ^ CRC(B) ^ CRC(C), for len(A) = len(B) = len(C) ดังนั้นสำหรับแผ่นครั้งเดียว :C = M ^ K with len(C) = len(M) = len(K)

โดยการคำนวณ CRC ของรหัสและใช้ความสัมพันธ์ก่อนหน้า: CRC(C) = CRC(M ^ K ^ 0) = CRC(M) ^ CRC(K) ^ CRC(0), for len(A) = len(B) = len(C)

เป้าหมาย:แก้ไข C ใน C2 เช่นนั้นCRC(C2 ^ K) = CRC(M2) = CRC(M)

C2ได้มาจากการดัดแปลงC xoring ด้วยK2

CRC(C2 ^ K) = CRC(C ^ K2 ^ K)
            = CRC(C) ^ CRC(K2) ^ CRC(K)

เป้าหมายคือการมี:

CRC(C2 ^ K) = CRC(M2) = CRC(M)

ดังนั้น

    CRC(M) = CRC(C) ^ CRC(K2) ^ CRC(K)
    CRC(K2) = CRC(M) ^ CRC(K) ^ CRC(C)
            = CRC(M) ^ CRC(K) ^ CRC(0) ^ CRC(0) ^ CRC(C)
            = CRC(M ^ K ^ 0) ^ CRC(0) ^ CRC(C)
            = CRC(C) ^ CRC(0) ^ CRC(C)
            = CRC(0)

หากต้องการแก้ไขรหัสโดยไม่ต้องแก้ไข CRC ของข้อความที่ชัดเจน สามารถสร้าง คีย์K2 ได้ และควรตรวจสอบ CRC(K2) = CRC(0)ซึ่งสามารถคำนวณได้ด้วยอัลกอริทึมที่อธิบายไว้ในส่วนก่อนหน้า

คุณสามารถตรวจสอบได้จากตัวอย่างนี้ (โดยเพิ่มรหัสก่อนหน้า):

cipher = lambda x,k:bytes([a^b for a,b in zip(x, k)])

crc = crc.CRC(0x1db710640, int('FF'*4, 16), int('FF'*4, 16))

message = b'CRC is so fun to break'
key     = b'Sha1 is better for crypto'
c = cipher(message, key)
#out: b'\x10:"\x12I\x1aSS\rE\x12\x01\x0bRT\tO\x10R\x06\x13\x12'
crc_m = crc.crc(message)
# '0x601784a9'
key_2 = b'\x42' * (len(message) - 4)
key_2 = crc.crc_inv(key_2, crc.crc(b'\x00'*len(message)))
# b'BBBBBBBBBBBBBBBBBB\xe9P\xc0x'
modified_c = cipher(c, key_2)
# b'Rx`P\x0bX\x11\x11O\x07PCI\x10\x16K\rR\xbbV\xd3j'
modified_m = cipher(modified_c, key)
# b'\x01\x10\x01b+1b1-b$7,b6-b \x9b5\xa1\x13'
crc.crc(modified_m)
# 0x601784a9 same as crc_m = crc.crc(message)
# We achieved our goal

ในส่วนนี้ได้รับการพิสูจน์แล้วว่า CRC ไม่ได้ให้ความสมบูรณ์ใด ๆ ในกรณีของการเข้ารหัสข้อมูลด้วยการเข้ารหัสสตรีม การใช้ CRC กับรหัสประเภทอื่นไม่มีรายละเอียด แต่ก็ไม่ควรใช้

บทสรุป

CRC เป็นวิธีที่ง่ายในการตรวจจับการเปลี่ยนแปลงในข้อมูล อย่างไรก็ตาม เป็นเรื่องง่ายมากที่จะสร้างข้อมูลอื่นด้วย CRC เดียวกัน แม้ว่าข้อมูลนั้นจะถูกเข้ารหัสและไม่ทราบข้อมูล CRC ดั้งเดิมก็ตาม ไม่ควรใช้ CRC เพื่อให้แน่ใจว่าข้อมูลมีความสมบูรณ์ในบริบทการเข้ารหัส

ขอบคุณที่อ่านบทความนี้ หวังว่าจะได้พบคุณเร็ว ๆ นี้

Face-sso (By K&O) หากท่านสนใจ เครื่องสแกนใบหน้ารุ่นต่างๆ หลากหลายรุ่น หรือ ติดตั้งระบบสแกนใบหน้า สามารถติดต่อสอบถามได้โดยตรง เรามีแอดมินคอยคอบคำถาม 24 ชั้วโมงที่ Line OA เครื่องสแกนใบหน้า สามารถ ขอราคาพิเศษได้ ตามงบประมาณที่เหมาะสม สอบถามได้สบายใจทั้ง เรื่องค่าบริการ ราคา และ งบประมาณ มั่นใจเพราะเป็นราคาที่สุด คุ้มค่าที่สุด

หากท่านมีความสนใจ บทความ หรือ Technology สามารถติดต่อได้ตามเบอร์ที่ให้ไว้ด้านล่างนี้
Tel.086-594-5494
Tel.095-919-6699

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *