Ray Morgan在PHP中提供了算法和执行。 算法具有几个冰层特性,即:
- the algorithm is deterministic, i.e., always produces the same obfuscated string for a given numeric ID value.
- the obfuscation is fully invertible, i.e., if you know (only) the obfuscated value, you can extract the underlying numeric ID
- doesn t yield any recognizable patterns (such as simple increasing sequences of integers)
- it can detect, whether an obfuscated ID string has been tampered with
The author itself explains the basic steps as follows
- Create a random number ($segment1) based on a hash of $id.
- Create a second random number ($segment2) based on a hash of $segment1.
- Alter $segment2 by adding or subtracting the value of $id.
- Make a third hash ($segment3) from $segment1 and the altered $segment2. This hash makes it possible to detect any alteration of the encoded ID.
- Concatenate the three segments into a string,
- and voilà – you have your obfuscated ID.
对于像我不喜欢PHP的人来说,算法的运行式共同利波港可以认为:
#-(and) (ql:quickload "ironclad")
#-(and) (ql:quickload "trivial-utf-8")
(defpackage "HASHID"
(:use "COMMON-LISP" "IRONCLAD" "TRIVIAL-UTF-8")
(:shadowing-import-from "COMMON-LISP" "NULL"))
(in-package "HASHID")
(defparameter +secret+ "Secret Password")
(defun sha1-hex-digest (string &optional (secret +secret+))
(let ((digest (make-digest :sha1)))
(update-digest digest (string-to-utf-8-bytes string))
(update-digest digest (string-to-utf-8-bytes secret))
(let* ((result (produce-digest digest))
(length (length result))
(char-length (* length 2))
(buffer (make-array char-length :element-type character))
(digits "0123456789ABCDEF"))
(loop
:with wp := 0
:for byte :across result
:do (setf (char buffer (prog1 wp (incf wp))) (char digits (ash byte -4)))
(setf (char buffer (prog1 wp (incf wp))) (char digits (logand byte 15)))
:finally (return buffer)))))
(defun obfuscate-id (identifier)
(let* ((segment-1 (subseq (sha1-hex-digest (format nil "~D" identifier)) 0 16))
(segment-2 (subseq (sha1-hex-digest (concatenate string segment-1)) 0 8))
(decimal (parse-integer segment-2 :radix 16))
(buried-id (if (< identifier decimal) (- decimal identifier) (+ decimal identifier)))
(new-segment-2 (format nil "~8, 0X" buried-id))
(segment-3 (subseq (sha1-hex-digest (concatenate string segment-1 new-segment-2)) 0 8)))
(concatenate string segment-1 new-segment-2 segment-3)))
(defun deobfuscate-id (string)
(let* ((segment-1 (subseq string 0 16))
(segment-2 (subseq string 16 24))
(segment-3 (subseq string 24))
(expected-2 (subseq (sha1-hex-digest segment-1) 0 8))
(expected-3 (subseq (sha1-hex-digest (concatenate string segment-1 segment-2)) 0 8)))
(and (string-equal segment-3 expected-3)
(let* ((v1 (parse-integer segment-2 :radix 16))
(v2 (parse-integer expected-2 :radix 16)))
(abs (- v1 v2))))))
Note, that the original implementation generated a base-64 encoded string from the obfuscated ID and used that as the actual value. I did omit this step here, but it should be simple to add, in particular, if your programming language of choice comes with base-64 support.