循環冗餘校驗(CRC, Cyclic Redundancy Check)是一種簡單快速的雜湊函數,可以藉由比對資料傳輸或是儲存前後的循環冗餘校驗碼,檢測其是否有錯誤發生。常被用來作為伺服器應用為目的來使用的Node.js,會有很大的機會需要使用CRC進行一些計算。可惜的是,Node.js雖然可以藉由內建的crypto模組來使用許多常見的雜湊函數計算資料的校驗和(checksum),但就是不支援CRC。那麼如果要在Node.js上計算CRC,該怎麼做比較好呢?



首先我們需要知道CRC演算法,實際上分了許多不同版本的函數,如8位元的CRC8、16位元的CRC16、32位元的CRC32、64位元的CRC64,CRC的位元數愈高,雜湊出來的數值碰撞機率自然會比較低,但會需要更多的時間去運算。但是CRC也不完全是使用位元數量去做變化,很有可能會遇到同樣的資料,A函式庫算出來的CRC32和B函式庫算出來的CRC32結果不同的情形。舉例來說,請看以下的PHP程式:

<?php
	header('Content-Type: text/plain');
	$data = 'test';
	$crc1 = dechex(crc32($data));
	$crc2 = bin2hex(mhash(MHASH_CRC32, $data));
	$crc3 = bin2hex(mhash(MHASH_CRC32B, $data));
	echo $crc1, ', ', $crc2, ', ', $crc3;
?>

執行結果為:

d87f7e0c, accf8b33, d87f7e0c

不曉得大家有沒有發現?同樣都是CRC32,字串test在上面程式中似乎可以算出d87f7e0c16accf8b3316這兩種結果。這是由於除了CRC的位元數量之外,還有其他變數在影響著CRC演算法的計算結果。

大體來說,若不包含準備進行CRC計算的資料的話,會影響CRC演算法結果的變數共有五個,分別是「位元數量」、「多項式」、「起始值」、「終止XOR值」和「是否反射」。

藉由調控這五個變數,可以衍生出各種版本的CRC雜湊函數,於是會細分成CRC8-ATM、CRC8-CDMA、CRC16-CCITT、CRC32-IEEE、CRC32-C、CRC64-ECMA、CRC64-ISO等等十分雜亂的CRC函數名稱。

順帶一提,mhash函式庫的CRC32函數算是比較特別的,並沒有其他常見的CRC函式庫可以算出跟mhash函式庫一樣的結果。於是在乎這點的程式設計師多半就會使用比較標準的CRC32B去計算CRC校驗和,以求在不同的平台上也能一致。但也是有沒發現到或是不在乎這個問題的程式設計師依舊使用mhash函式庫的CRC32函數,導致與其他系統串接時雙方可能都會有一些障礙。

回到Node.js的主題上,若要在Node.js上實作CRC演算法,最好是能將先前提到的五個變數考慮進去,確保演算法去串接其他人實作的CRC函數之相容性。至於實作的方式本篇文章將不會介紹,本篇文章要介紹的是node-crc這個模組。

node-crc

node-crc是一個使用Node.js 8之後才支援的N-API所開發的模組,使用Rust語言實作出各種不同版本的CRC函數,並且可用「位元數量」、「多項式」、「起始值」、「終止XOR值」和「是否反射」這五個變數來控制輸出結果。

npmjs.com

npm 安裝指令

npm install node-crc

使用方法

計算CRC校驗和

使用模組的crc函數可以傳入「位元數量」、「多項式」、「起始值」、「終止XOR值」和「是否反射」這五個變數以及要計算的資料Buffer來計算出該資料的CRC校驗和,結果會以Buffer的型態回傳。

import { crc } from "node-crc";

const result = crc(0x00864cfb, 0x00000000, 24, 0x00b704ce, 0x00000000, 0x00000000, 0x00000000, false, Buffer.from("hello", "utf8")).toString("hex");
// Arguments: low bits of expression, high bits of expression, the length of bits, low bits of the initial value, high bits of the initial value, low bits of the final xor value, high bits of the final xor value, reflection, the source data buffer

模組同時也提供數個常見的CRC函數,可直接被呼叫使用,列表如下:

  • crc3gsm
  • crc4itu
  • crc4interlaken
  • crc5epc
  • crc5itu
  • crc5usb
  • crc6cdma2000_a
  • crc6cdma2000_b
  • crc6darc
  • crc6gsm
  • crc6itu
  • crc7
  • crc7umts
  • crc8
  • crc8cdma2000
  • crc8darc
  • crc8dvb_s2
  • crc8ebu
  • crc8icode
  • crc8itu
  • crc8maxim
  • crc8rohc
  • crc8wcdma
  • crc10
  • crc10cdma2000
  • crc10gsm
  • crc11
  • crc12
  • crc12cdma2000
  • crc12gsm
  • crc13bbc
  • crc14darc
  • crc14gsm
  • crc15can
  • crc15mpt1327
  • crc16
  • crc16ccitt_false
  • crc16aug_ccitt
  • crc16buypass
  • crc16cdma2000
  • crc16dds_110
  • crc16dect_r
  • crc16dect_x
  • crc16dnp
  • crc16en_13757
  • crc16genibus
  • crc16maxim
  • crc16mcrf4cc
  • crc16riello
  • crc16t10_dif
  • crc16teledisk
  • crc16tms13157
  • crc16usb
  • crc_a
  • crc16kermit
  • crc16modbus
  • crc16_x25
  • crc16xmodem
  • crc17can
  • crc21can
  • crc24
  • crc24ble
  • crc24flexray_a
  • crc24flexray_b
  • crc24lte_a
  • crc24lte_b
  • crc24os9
  • crc30cdma
  • crc32 (在mhash中的名稱為CRC32B)
  • crc32mhash (在mhash中的名稱為CRC32)
  • crc32bzip2
  • crc32c
  • crc32d
  • crc32mpeg2
  • crc32posix
  • crc32q
  • crc32jamcrc
  • crc32xfer
  • crc40gsm
  • crc64
  • crc64iso
  • crc64we
  • crc64jones

用法如下:

import { crc32, crc64 } from "node-crc";

const result = crc32(Buffer.from("hello", "utf8")).toString("hex");
const result2 = crc64(Buffer.from("world", "utf8")).toString("hex");