ตรวจสอบความซ้ำซ้อนแบบวนซ้ำ 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)+�8 | 0x57 |
กลับกัน | (x8+x7+x6+x4+x2) + 1(�8+�7+�6+�4+�2)+1 | 0xEA |
รายชื่อพหุนามสำหรับ 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