Update of /project/crypticl/cvsroot/crypticl/src In directory clnet:/tmp/cvs-serv25386
Modified Files: sha256.lisp Log Message: Test CLOS api. Put api at the head of the file. Cleanup.
--- /project/crypticl/cvsroot/crypticl/src/sha256.lisp 2007/01/16 00:53:33 1.5 +++ /project/crypticl/cvsroot/crypticl/src/sha256.lisp 2007/01/16 23:43:12 1.6 @@ -4,6 +4,7 @@ ;;;; Description: The SHA-256 hash algorithm ;;;; Author: Taale Skogan ;;;; Distribution: See the accompanying file LICENSE. +;;;; Initial version: 16.01.2007
;;; Based on reference [1] from http://csrc.nist.gov/cryptval/shs.htm @@ -17,9 +18,107 @@
(in-package crypticl)
-;;; SHA-256 uses a sequence of 64 32-bit word constants. They -;;; are referred to as K0,...,K63. -(defvar *sha-256-constants* +;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Low-level function API +;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defun sha-256-on-octet-vector (octet-vector) + "Return SHA-256 hash of byte array/octet vector" + (sha-256-encode + (make-buffer-filler + (make-byte-array-reader-function octet-vector)))) + +(defun sha-256-on-string (string) + "Return SHA-256 hash of a string. + +NB! With this function the hash value depends on the encoding of string +and implementation specific details of the Common Lisp distribution you +are using (see make-string-reader-function and its' use of char-code +for more details). For more control, decode the string to a byte array +yourself and use the byte array interface sha-256-on-octet-vector instead. +" + (sha-256-encode + (make-buffer-filler + (make-string-reader-function string)))) + +(defun sha-256-on-octet-stream (stream) + "Return SHA-256 hash of stream." + (sha-256-encode + (make-buffer-filler #'(lambda () (read-byte stream nil))))) + +(defun sha-256-file (path) + "Return SHA-256 hash of a file." + (with-open-file (str path) + (sha-256-on-octet-stream str))) + + +;;;;;;;;;;;;; +;;; +;;; CLOS API +;;; +;;;;;;;;;;;;; +(defun make-SHA-256 () + "Constructor for the SHA-256 class" + (let ((obj (make-instance 'SHA-256 :algorithm "SHA-256"))) + (reset obj) + obj)) + +(defmethod reset ((obj SHA-256)) + (initial-sha-256-hash-value (a obj) (b obj) (c obj) (d obj) + (e obj) (f obj) (g obj) (h obj)) + (setf (octet-count obj) 0 + (leftover-count obj) 0 + (called-hash obj) nil + (fresh obj) t)) + +(defmethod hash ((obj SHA-256) &optional data (start 0) (end (length data))) + "Return SHA-256 hash of all bytes added so far. + +Note that calling hash on an empty object object makes no sense but the spec +seems to be that we run algorithm on the initial state and return a full +256 bits hash even when the message length is 0. + +Calling it a second time without adding data returns the previous value. +" + (when (and (not data) (called-hash obj)) + ;; Return previous hash value when we have one and no data has been + ;; added since last call to hash. + (return-from hash (sha-256-make-octet-vector + (a obj) (b obj) (c obj) (d obj) + (e obj) (f obj) (g obj) (h obj)))) + (when data + (typecase data + (vector (sha-256-add-octet-vector obj data start end)) + (otherwise + (error "Hash on data type ~A not implemented." (type-of data))))) + + (setf (called-hash obj) t) + (sha-256-final obj)) + +(defmethod update ((obj SHA-256) (octet-vector vector) + &optional (start 0) (end (length octet-vector))) + "Add bytes to SHA-256 hash object. + +Will compute the intermediate hash value and not store the input. Useful +for hashing a large file that doesn't fit in memory or a data stream. + +When all bytes have been added you get the hash value by calling the +hash method." + ;; Reset object if we have called hash + (when (called-hash obj) + (reset obj)) + + (sha-256-add-octet-vector obj octet-vector start end) + (setf (fresh obj) nil)) + + +;;;;;;;;;;;;;;;;;;; +;;; +;;; Implementation +;;; +;;;;;;;;;;;;;;;;;;; +(defparameter *sha-256-constants* (make-array 64 :element-type '(unsigned-byte 32) :initial-contents @@ -38,7 +137,8 @@ #x19a4c116 #x1e376c08 #x2748774c #x34b0bcb5 #x391c0cb3 #x4ed8aa4a #x5b9cca4f #x682e6ff3 #x748f82ee #x78a5636f #x84c87814 #x8cc70208 - #x90befffa #xa4506ceb #xbef9a3f7 #xc67178f2))) + #x90befffa #xa4506ceb #xbef9a3f7 #xc67178f2)) + "SHA-256 uses a sequence of 64 32-bit word constants.")
(defun sha-256-constant (i) (aref *sha-256-constants* i)) @@ -114,7 +214,7 @@ g (32-add g gg) h (32-add h hh))))
- ;; Return hash value. + ;; Return hash value as array. (sha-256-make-octet-vector a b c d e f g h)))
(defmacro sha-256-make-octet-vector (a b c d e f g h) @@ -193,36 +293,11 @@ w))
-;;;;;;;;;;;;; -;;; -;;; Low-level function API -;;; -;;;;;;;;;;;;; -(defun sha-256-on-octet-vector (octet-vector) - "Return SHA-256 hash of byte array/octect vector" - (sha-256-encode - (make-buffer-filler - (make-byte-array-reader-function octet-vector)))) - -(defun sha-256-on-string (string) - "Return SHA-256 hash of a string. - -NB! With this function the hash value depends on the encoding of string -and implementation specific details of the Common Lisp distribution you -are using (see make-string-reader-function and its' use of char-code -for more details). For more control, decode the string to a byte array -yourself and use the byte array interface sha-256-on-octet-vector instead. -" - (sha-256-encode - (make-buffer-filler - (make-string-reader-function string)))) - - -;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;; ;;; ;;; CLOS internals ;;; -;;;;;;;;;;;;; +;;;;;;;;;;;;;;;;;;; (defclass SHA-256 (Hash) ((octet-count :accessor octet-count ;octets processed so far :initform 0) @@ -243,6 +318,7 @@ (g :accessor g) (h :accessor h)))
+ (defmethod store-leftover ((obj SHA-256) octet-vector offset end octet-count) "Store leftover bytes between calls to update" (let ((leftover-offset (leftover-count obj)) @@ -279,7 +355,6 @@ (sha-256-encode-block obj vec))))
- (defmethod sha-256-final ((obj SHA-256)) (let ((vec (make-array 16 :element-type '(unsigned-byte 32))) (buffer-filler @@ -311,6 +386,7 @@ (g obj) (32-add (g obj) gg) (h obj) (32-add (h obj) hh))))
+ ;;; TODO identical to SHA1 method i sha.lisp so reuse (defmethod fill-vector ((obj SHA-256) return-vector octet-vector start) "Return the next 512 bits for hashing. @@ -353,79 +429,21 @@ ;; Return offset in input vector. (+ start used)))
-;;;;;;;;;;;;; -;;; -;;; CLOS API -;;; -;;;;;;;;;;;;; -(defun make-SHA256 () - "Constructor for the SHA-256 class" - (let ((obj (make-instance 'SHA-256 :algorithm "SHA-256"))) - (reset obj) - obj)) - -(defmethod reset ((obj SHA-256)) - (initial-sha-256-hash-value (a obj) (b obj) (c obj) (d obj) - (e obj) (f obj) (g obj) (h obj)) - (setf (octet-count obj) 0 - (leftover-count obj) 0 - (called-hash obj) nil - (fresh obj) t)) - -(defmethod hash ((obj SHA-256) &optional data (start 0) (end (length data))) - "Return SHA-256 hash of all bytes added so far. - -Note that calling hash on an empty object object makes no sense and we -return nil. - -XXX Calling it a second time without adding data? The same value as the first -time? -" - (when (and (fresh obj) (not data)) - ;; Returning a hash value on no data makes no sense. - (return-from hash nil)) - (when (and (not data) (called-hash obj)) - ;; Return previous hash value when we have one and no data has been - ;; added since last call to hash. - (return-from hash (sha-256-make-octet-vector (a obj) (b obj) (c obj) (d obj) - (e obj) (f obj) (g obj) (h obj)))) - (when data - (typecase data - (vector (sha-256-add-octet-vector obj data start end)) - (otherwise - (error "Hash on data type ~A not implemented." (type-of data))))) - - (setf (called-hash obj) t) - (sha-256-final obj)) - - -(defmethod update ((obj SHA-256) (octet-vector vector) - &optional (start 0) (end (length octet-vector))) - "Add bytes to SHA-256 hash object. - -Will compute the intermediate hash value and not store the input. Useful -for hashing a large file that doesn't fit in memory or a data stream. - -When all bytes have been added you get the hash value by calling the -hash method." - ;; Reset object if we have called hash - (when (called-hash obj) - (reset obj)) - - (sha-256-add-octet-vector obj octet-vector start end) - (setf (fresh obj) nil)) - - -(register-constructor 'SHA256 #'make-SHA256)
;;;;;;;;;;;;;;;;;; ;;;; ;;;; Tests ;;;; ;;;;;;;;;;;;;;;;;; -(defun test-sha-256 () +(defun run-all-sha-256-tests () + "Run all tests." (test-sha-256-short) - (test-sha-256-long)) + (test-sha-256-long) + (run-shavs-256)) + +(defun test-SHA-256 () + "Run these tests at load time." + (test-sha-256-short))
(defun test-sha-256-short () "Test vector 1 and 2 are taken from reference FIPS 180-2." @@ -436,11 +454,11 @@ (list "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1") ))) - (format t "Testing SHA-256.~%") - (dolist (x test-list (format t "Short messages OK.~%")) + (format t "~&Testing SHA-256 short vectors...") + (dolist (x test-list (format t "OK.")) (let ((in (first x)) (ex (second x)) - (obj (make-SHA256))) + (obj (make-SHA-256))) ;; low-level API (assert (string= (hex (sha-256-on-string in)) ex) () "sha-256 test for input string ~A~%" in) @@ -527,18 +545,57 @@ (defun shavs-256 (len msg md) (when (= len 0) (setf msg "")) - (assert (string= (hex (sha-256-on-octet-vector (hexo msg))) - md) - nil (format nil "Failed on msg ~A" msg))) + (let ((bts (hexo msg)) ; msg as bytes + (obj (make-SHA-256))) + + ;; Low-level API + (assert (string= (hex (sha-256-on-octet-vector bts)) md) () + "sha-256 failed low-level hash on msg '~A'" msg) + + ;; CLOS API + ;; hash all in one + (assert (string= (hex (hash obj bts)) md) () + "sha-256 failed all-in-on-hash on msg '~A'" msg) + + ;; update with all, then hash + (reset obj) + (update obj bts) + (assert (string= (hex (hash obj)) md) () + "sha-256 failed single-update-hash on msg '~A'" msg)
+ ;; update multiple times, then hash + (reset obj) + (let ((size (length bts))) + (do* ((pos 0 (+ pos next)) + (next (shavs-next-update-size pos size) + (shavs-next-update-size pos size)) + (end (+ pos next) (+ pos next))) + ((> end size)) + ;;(format t "size ~2@A pos ~2@A next ~2@A end ~2@A~%" size pos next end) + (update obj (subseq bts pos end)))) + (assert (string= (hex (hash obj)) md) () + "sha-256 failed multiple-updates-hash on msg '~A'" md) + ))
-(defun run-shavs (&optional (path "../test/SHA256ShortMsg.txt")) +(defun shavs-next-update-size (pos size) + ;; 60 a bit bigger than the msg block size (64 bytes) + (let ((next (random 100)) + (leftover (- size pos))) + (max 1 ; always advance at least once + (if (>= next leftover) + leftover + next)))) + +(defun run-shavs-on-string (string) + (with-input-from-string (str string) + (shavs str))) + +(defun run-shavs-256 (&optional (path "../test/SHA256ShortMsg.txt")) "Verify SHA-256 against SHAVS test vectors from file.
NB The long test vectors are in the file ../test/SHA256LongMsg.txt" (with-open-file (str path) (shavs str)))
-(defun run-shavs-on-string (string) - (with-input-from-string (str string) - (shavs str))) \ No newline at end of file + +(register-constructor 'SHA-256 #'make-SHA-256) \ No newline at end of file