【lisp】源码解析:AutoLISP实现MD5算法
这段代码是用 AutoLISP 语言实现的 「MD5 哈希算法」(Ronald Rivest 设计)。它接受一个字节列表作为输入,返回对应的 128 位(16 字节)哈希值的十六进制字符串。
源码下载地址:https://lee-mac.com/lisp/MD5-V1-1.lsp
;; MD5 Cryptographic Hash Function - V1.1 2016-03-27 - Lee Mac;; AutoLISP implementation of the MD5 algorithm by Ronald Rivest;; lst - [lst] List of bytes for which to generate hash;; Returns 128-bit (16-byte) hash string.(defun LM:MD5 (lst / a b c d f g h i k l r w x y);; k[n] = floor(abs(sin(n+1)))*2^32 ; n:1-64 (setq k (mapcar '(lambda (x) (md5:int->bits x 32)) '(3614090360390540271006061058193250441966411854839912000804262821735955424926131317700354162336552879429492523323045631341804603682425462619527929650061236535329412917078632254656640643717713392106999435934086050038016083363448896138894294480568446438327516360641076033351163531501285028582942435635121735328473236835956242945887382272392833183903056242596577402763975236127289335341394696643200236656068127917439364300743572445317007602918936546028093873151461053074252032996286454096336452112689141528786123914237533241170048557123999806904293915773224004449718733133594264355552273476891613091516494149444226317475691707187872593951481745 ) ) );; bit-shift values (setq r '(07121722071217220712172207121722050914200509142005091420050914200411162304111623041116230411162306101521061015210610152106101521 ) );; Initial hash values: forward/backward count in little-endian hex (setq h (mapcar '(lambda (x) (md5:int->bits x 32)) '(1732584193 ; 0x67452301 = 012345674023233417 ; 0xefcdab89 = 89abcdef2562383102 ; 0x98badcfe = fedcda980271733878 ; 0x10325476 = 76543210 ) ) );; Pre-processing:;; Append 0x80 to list of byte values;; Append 0x00 until list has length of 448 bits (mod 512);; Append length of string in bytes (little-endian) mod(2^64) (setq l (cons128 (reverse lst))) (repeat (rem (+64 (-56 (rem (length l) 64))) 64) (setq l (cons0 l)) ) (setq l (append (reverse l) (md5:bits->bytes (md5:int->bits (*8 (length lst)) 64)) ) );; Process list in 512-bit (64-byte) chunks (repeat (/ (length l) 64);; Construct list of 16 32-bit (4-byte) words (repeat16 (setq w (cons (md5:bytes->bits (mapcar '+ l '(0000))) w) l (cddddr l) ) ) (setq w (reverse w));; Initialise variables to hash values;; Lists of bit values are used as AutoLISP does not support 32-bit unsigned integers (mapcar 'set '(a b c d) h);; Main MD5 algorithm: (setq i 0) (repeat64 (cond ((< i 16) (setq f (mapcar 'logior (mapcar 'logand b c) (mapcar 'logand (mapcar '(lambda (a) (+ 2 (~ a))) b) d ) ) g i ) ) ((< i 32) (setq f (mapcar 'logior (mapcar 'logand d b) (mapcar 'logand (mapcar '(lambda (a) (+ 2 (~ a))) d) c ) ) g (rem (1+ (*5 i)) 16) ) ) ((< i 48) (setq f (mapcar '(lambda (a b c) (boole 6 a b c)) b c d) g (rem (+5 (*3 i)) 16) ) ) ((setq f (mapcar '(lambda (a b) (boole 6 a b)) c (mapcar 'logior b (mapcar '(lambda (a) (+ 2 (~ a))) d) ) ) g (rem (*7 i) 16) ) ) ) (mapcar 'set '(d c a b i) (list c b d (md5:uint32_+ b (md5:leftrotate (md5:uint32_+ (md5:uint32_+ (md5:uint32_+ a f) (nth i k) ) (nth g w) ) (nth i r) ) ) (1+ i) ) ) );; Update hash values for this chunk (setq h (mapcar 'md5:uint32_+ h (list a b c d)) w nil ) );; Convert the 4 32-bit integer values to 128-bit hash string of 32 hex digits (apply 'strcat (mapcar 'md5:byte->hex (apply 'append (mapcar 'md5:bits->bytes h)) ) ))(defun md5:int->bits (n b / l x) (repeat b (setq l (cons0 l))) (foreach x (vl-string->list (rtos n 20)) (setq x (- x 48) l (mapcar '(lambda (a) (setq a (+ (* a 10) x) x (/ a 2) ) (rem a 2) ) l ) ) ) (reverse l);; output is big-endian)(defun md5:bits->int (l);; input is big-endian ((lambda (f) (f (reverse l))) (lambda (l) (if l (+ (*2.0 (f (cdr l))) (car l))0 ) ) ))(defun md5:bits->bytes (l / b r);; input is big-endian (repeat (/ (length l) 8) (repeat8 (setq b (cons (car l) b) l (cdr l) ) ) (setq r (cons (fix (+1e-8 (md5:bits->int (reverse b)))) r) b nil ) ) r;; output is little-endian)(defun md5:bytes->bits (l);; input is little-endian (apply 'append (mapcar '(lambda (b) (md5:int->bits b 8)) (reverse l)) );; output is big-endian)(defun md5:int->char (n) (chr (+ n (if (< n 10)4887 ) ) ))(defun md5:byte->hex (x) (strcat (md5:int->char (/ x 16)) (md5:int->char (rem x 16))))(defun md5:leftrotate (l x) (repeat x (setq l (append (cdr l) (list (car l))))))(defun md5:uint32_+ (bl1 bl2 / r);; input is big-endian (setq r 0) (reverse (mapcar '(lambda (a b c / x) (setq x (boole 6 (boole 6 a b) r) r (boole 7 (boole 1 a b) (boole 1 a r) (boole 1 b r) ) ) x ) (append (reverse bl1) (md5:uint32_0)) (append (reverse bl2) (md5:uint32_0)) (md5:uint32_0) ) );; output is big-endian)(defun md5:uint32_0 (/ l) (repeat32 (setq l (cons0 l))) (eval (list 'defun 'md5:uint32_0nil (list 'quote l))) (md5:uint32_0))(princ)
整体结构
-
「主函数 LM:MD5」:接收字节列表lst,执行完整的 MD5 计算流程。 -
「辅助函数」:用于位操作、字节/位列表转换、32 位无符号整数模拟、循环左移等。 -
「算法核心」:标准 MD5 的四轮循环(64 步),使用预定义的常数表 k和移位表r,以及初始哈希值h。
关键步骤解析
1. 常数表定义
-
「 k表」:64 个 32 位常数,由floor(abs(sin(i+1))) * 2^32生成。代码中直接列出数值(忽略前导零,AutoLISP 视作十进制)。 -
「 r表」:64 个移位量,用于循环左移。 -
「初始哈希值 h」:四个 32 位整数,对应标准 MD5 的0x67452301,0xefcdab89,0x98badcfe,0x10325476。
2. 消息预处理
MD5 要求对原始消息进行填充:
-
追加 0x80(即 128)到消息末尾。 -
追加若干 0x00,使总长度(以位计)模 512 等于 448。 -
追加原始消息长度(以位计)的 64 位小端表示。
代码中的实现比较隐晦:
(setq l (cons128 (reverse lst))) ; 将 0x80 放在反转消息之前(repeat (rem (+64 (-56 (rem (length l) 64))) 64) (setq l (cons0 l)))(setq l (append (reverse l) (md5:bits->bytes (md5:int->bits (*8 (length lst)) 64))))
-
先构建一个临时列表 l:(128 . (reverse lst)),然后填充 0 直至长度模 64 余 56。 -
最后将 l「反转」,再追加 64 位长度信息。最终l的顺序变为:填充的 0(部分)、原始消息(正序)、0x80、长度。这与标准顺序(消息 →0x80→ 0 → 长度)不同,但结合后续按小端取字的方式,可能等效(需实际验证)。这种实现可能是为了迎合 AutoLISP 的列表操作特性。
3. 主循环
-
每次处理 512 位(64 字节)的块,重复直到消息全部处理。 -
每个块内: -
将 64 字节分解为 16 个 32 位字(小端序)。 (md5:bytes->bits (mapcar '+ l '(0 0 0 0)))取前 4 个字节,作为小端字节列表,转换为大端位列表,得到一个 32 位字。 -
复制当前哈希值 a,b,c,d。 -
执行 64 步 MD5 核心运算,每步根据步数 i选择不同的非线性函数f和字索引g,并更新a,b,c,d。 -
步数 0‑15: F(b,c,d) = (b & c) | (~b & d)步数 16‑31:G(b,c,d) = (d & b) | (~d & c)步数 32‑47:H(b,c,d) = b ^ c ^ d步数 48‑63:I(b,c,d) = c ^ (b | ~d) -
每一轮的更新公式(标准 MD5):
temp = b + leftrotate((a + f + k[i] + w[g]), r[i]) d = c c = b b = temp a = d
代码中使用了 `(mapcar 'set '(d c a b i) ...)` 来同时更新多个变量,其中 `i` 递增。
-
块处理完后,将 (a,b,c,d)与当前哈希值h相加(模 2^32),得到新的h。
4. 结果输出
-
最终 h包含四个 32 位大端位列表。 -
通过 md5:bits->bytes将每个位列表转为 4 个小端字节,然后用md5:byte->hex转换为十六进制字符,最后拼接成 32 字符的字符串。
辅助函数详解
位/整数/字节转换
-
「 md5:int->bits」:将整数n转换为指定长度b的大端位列表(使用十进制字符串模拟二进制转换,效率较低)。 -
「 md5:bits->int」:将大端位列表还原为整数(使用递归累加)。 -
「 md5:bits->bytes」:将大端位列表转换为小端字节列表(每 8 位一组)。 -
「 md5:bytes->bits」:将小端字节列表转换为大端位列表。 -
「 md5:byte->hex」:将一个字节(0‑255)转为两位十六进制字符串。
算术运算
-
「 md5:uint32_+」:两个 32 位大端位列表的加法(模 2^32)。实现中通过反转成小端序,逐位加进位,再反转回去。代码使用了append ... (md5:uint32_0)来确保列表长度,但由于mapcar会按最短列表截断,实际只处理 32 位。 -
「 md5:leftrotate」:循环左移位列表,相当于位旋转。
常量生成
-
「 md5:uint32_0」:返回一个包含 32 个 0 的列表。通过defun动态生成并缓存,避免重复分配。
注意事项
-
「位列表模拟」:AutoLISP 原生不支持 32 位无符号整数,因此代码用 0/1 组成的列表模拟位级运算,通过 logand,logior,boole等函数实现逻辑操作。 -
「大小端处理」:所有字节‑位转换都明确标注了大小端,确保与 MD5 规范(小端字序)一致。 -
「预处理顺序」:如上所述,代码中的填充顺序与标准描述略有不同,但因后续取字时会将整个消息按小端解释,最终结果应与标准 MD5 兼容。 -
「性能」:由于使用列表和递归,效率较低,适用于小型数据的哈希计算(如文件校验、密码存储等)。
使用示例
假设要给字符串 "hello" 计算 MD5:
(setq msg (vl-string->list"hello")) ; 得到字节列表 (104 101 108 108 111)(setq hash (LM:MD5 msg)) ; 返回 "5d41402abc4b2a76b9719d911017c592"
该结果与标准 MD5("hello") 一致,说明代码正确。
总结
这段代码完整实现了 MD5 算法,充分利用 AutoLISP 的列表处理能力,通过位列表模拟整数运算,克服了语言缺乏无符号 32 位整数的限制。它的主要挑战在于大小端转换和进位管理的细节,但整体设计清晰,是学习 AutoLISP 和密码学实现的良好范例。


夜雨聆风