Component digest

You are here: All Systems / ironclad / digest

;;;; digest.lisp -- common functions for hashing

(in-package :crypto)


;;; defining digest (hash) functions

;;; general inlinable functions for implementing the higher-level functions

(declaim (inline digest-sequence-body digest-file-body))

(defun digest-sequence-body (sequence state-creation-fn
                             state-update-fn state-finalize-fn
                             &key (start 0) end buffer (buffer-start 0)
)

  (declare (type (vector (unsigned-byte 8)) sequence) (type index start))
  (let ((state (funcall state-creation-fn)))
    #+cmu
    ;; respect the fill-pointer
   (let ((end (or end (length sequence))))
      (declare (type index end))
      (lisp::with-array-data ((data sequence) (real-start start) (real-end end))
        (declare (ignore real-end))
        (funcall state-update-fn state data
                 :start real-start :end (+ real-start (- end start))
)
)
)

    #+sbcl
    ;; respect the fill-pointer
   (let ((end (or end (length sequence))))
      (sb-kernel:with-array-data ((data sequence) (real-start start) (real-end end))
        (declare (ignore real-end))
        (funcall state-update-fn state data
                 :start real-start :end (+ real-start (- end start))
)
)
)

    #-(or cmu sbcl)
    (let ((real-end (or end (length sequence))))
      (declare (type index real-end))
      (funcall state-update-fn state sequence
               :start start :end (or real-end (length sequence))
)
)

    (funcall state-finalize-fn state buffer buffer-start)
)
)


(eval-when (:compile-toplevel :load-toplevel)
(defconstant +buffer-size+ (* 128 1024))
)
; EVAL-WHEN

(deftype buffer-index () `(integer 0 (,+buffer-size+)))

(defun update-digest-from-stream (digest stream)
  (cond
    ((let ((element-type (stream-element-type stream)))
       (or (equal element-type '(unsigned-byte 8))
           (equal element-type '(integer 0 255))
)
)

     (let ((read-buffer (make-array +buffer-size+
                                    :element-type '(unsigned-byte 8)
)
)
)

       (declare (type (simple-array (unsigned-byte 8) (#.+buffer-size+))
                      read-buffer
)
)

       (declare (dynamic-extent read-buffer))
       (loop for n-bytes = (read-sequence read-buffer stream)
          do (update-digest digest read-buffer :end n-bytes)
          until (< n-bytes +buffer-size+)
          finally (return digest)
)
)
)

    (t
     (error "Unsupported stream element-type ~S for stream ~S."
            (stream-element-type stream) stream
)
)
)
)


(defun digest-file-body (pathname state-creation-fn
                         state-update-fn state-finalize-fn
                         &key buffer (buffer-start 0)
)

  (with-open-file (stream pathname :element-type '(unsigned-byte 8)
                          :direction :input
                          :if-does-not-exist :error
)

    (let ((state (funcall state-creation-fn)))
      (update-digest-from-stream state stream)
      (funcall state-finalize-fn state buffer buffer-start)
)
)
)



;;; macros for "mid-level" functions

(defmacro define-digest-registers ((digest-name &key (endian :big) (size 4)) &rest registers)
  (let* ((struct-name (intern (format nil "~A-REGS" digest-name)))
         (constructor (intern (format nil "INITIAL-~A" struct-name)))
         (copier (intern (format nil "%COPY-~A" struct-name)))
         (digest-fun (intern (format nil "~AREGS-DIGEST" digest-name)))
         (register-bit-size (* size 8))
         (digest-size (* size (length registers)))
         (ref-fun (ubref-fun-name register-bit-size (eq endian :big)))
)

    `(progn
       (eval-when (:compile-toplevel :load-toplevel :execute)
         (defstruct (,struct-name
                      (:type (vector (unsigned-byte ,register-bit-size)))
                      (:constructor ,constructor ())
                      (:copier ,copier)
)

           ,@registers
)

         ;; LispWorks properly defines STRUCT-NAME as a type with DEFSTRUCT,
        ;; so just avoid gratuitous warnings here.
        #-lispworks
         (deftype ,struct-name ()
           '(simple-array (unsigned-byte ,register-bit-size) (,(length registers)))
)
)

       (defun ,digest-fun (regs buffer start)
         (declare (type ,struct-name regs)
                  (type (integer 0 ,(- array-dimension-limit digest-size)) start)
                  ,(burn-baby-burn)
)

         (flet ((stuff-registers (buffer start)
                  (declare (type (simple-array (unsigned-byte 8) (*))))
                  (setf ,@(loop for (reg value) in registers
                               for index from 0 by size
                               nconc `((,ref-fun buffer (+ start ,index))
                                       (,(intern (format nil "~A-REGS-~A" digest-name reg)) regs)
)
)
)

                  buffer
)
)

           (declare (inline stuff-registers))
           (cond
             (buffer
              (stuff-registers buffer start)
)

             (t (stuff-registers (make-array ,digest-size :element-type '(unsigned-byte 8)) 0))
)
)
)
)
)
)


(defmacro define-digest-updater (digest-name &body body)
  (let ((fun-name (intern (format nil "UPDATE-~A-STATE" digest-name)))
        (state-name (intern (format nil "~A-STATE" digest-name)))
)

    (destructuring-bind (maybe-doc-string &rest rest) body
      `(defun ,fun-name (state sequence &key (start 0) (end (length sequence)))
         ,@(when (stringp maybe-doc-string)
             `(,maybe-doc-string)
)

         (declare (type ,state-name state))
         (declare (type (simple-array (unsigned-byte 8) (*)) sequence))
         (declare (type index start end))
         ,(hold-me-back)
         ,@(if (stringp maybe-doc-string)
               rest
               body
)
)
)
)
)


(defmacro define-digest-finalizer (digest-name digest-size &body body)
  (let ((fun-name (intern (format nil "FINALIZE-~A-STATE" digest-name)))
        (inner-fun-name (intern (format nil "%FINALIZE-~A-STATE" digest-name)))
        (finalized-p (intern (format nil "~A-STATE-FINALIZED-P" digest-name)))
        (reg-digest-fun (intern (format nil "~AREGS-DIGEST" digest-name)))
        (state-name (intern (format nil "~A-STATE" digest-name)))
)

    (destructuring-bind (maybe-doc-string &rest rest) body
      `(progn
         (defun ,fun-name (state &optional buffer buffer-start)
           ,@(when (stringp maybe-doc-string)
                   `(,maybe-doc-string)
)

           (declare (type ,state-name state))
           (declare (type (or (simple-array (unsigned-byte 8) (*)) null) buffer))
           (cond
             (buffer
              ;; verify that the buffer is large enough
             (if (<= ,digest-size (- (length buffer) buffer-start))
                  (,inner-fun-name state buffer buffer-start)
                  (error 'insufficient-buffer-space
                         :buffer buffer :start buffer-start
                         :length ,digest-size
)
)
)

             (t
              (,inner-fun-name state nil 0)
)
)
)

         (defun ,inner-fun-name (state %buffer buffer-start)
           ,(hold-me-back)
           (macrolet ((finalize-registers (state regs)
                        `(setf (,',finalized-p ,state)
                               (,',reg-digest-fun ,regs %buffer buffer-start)
)
)
)

             ,@(if (stringp maybe-doc-string)
                   rest
                   body
)
)
)
)
)
)
)


;;; high-level generic function drivers

;;; These three functions are intended to be one-shot ways to digest
;;; an object of some kind.  You could write these in terms of the more
;;; familiar digest interface below, but these are likely to be slightly
;;; more efficient, as well as more obvious about what you're trying to
;;; do.
(defgeneric digest-file (digest-name pathname &key digest digest-start)
  (:documentation "Return the digest of the contents of the file named
by PATHNAME using the algorithm DIGEST-NAME.

If DIGEST is provided, the digest will be placed into DIGEST starting at
DIGEST-START.  DIGEST must be a (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)).
An error will be signaled if there is insufficient room in DIGEST."
)
)


(defgeneric digest-stream (digest-name stream &key digest digest-start)
  (:documentation "Return the digest of the contents of STREAM using
the algorithm DIGEST-NAME.  STREAM-ELEMENT-TYPE of STREAM should be
 (UNSIGNED-BYTE 8).

If DIGEST is provided, the digest will be placed into DIGEST starting at
DIGEST-START.  DIGEST must be a (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)).
An error will be signaled if there is insufficient room in DIGEST."
)
)


(defgeneric digest-sequence (digest-name sequence
                                         &key start end
                                         digest digest-start
)

  (:documentation "Return the digest of the subsequence of SEQUENCE
specified by START and END using the algorithm DIGEST-NAME.  For CMUCL
and SBCL, SEQUENCE can be any vector with an element-type of
 (UNSIGNED-BYTE 8); for other implementations, SEQUENCE must be a
SIMPLE-ARRAY.

If DIGEST is provided, the digest will be placed into DIGEST starting at
DIGEST-START.  DIGEST must be a (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)).
An error will be signaled if there is insufficient room in DIGEST."
)
)


;;; These four functions represent the common interface for digests in
;;; other crypto toolkits (OpenSSL, Botan, Python, etc.).  You obtain
;;; some state object for a particular digest, you update it with some
;;; data, and then you get the actual digest.  Flexibility is the name
;;; of the game with these functions.
(defgeneric make-digest (digest-name)
  (:documentation "Return a digest object which uses the
algorithm DIGEST-NAME."
)
)


(defmethod make-digest (digest-name)
  (error 'unsupported-digest :name digest-name)
)


(defgeneric copy-digest (digester)
  (:documentation "Return a copy of DIGESTER.  The copy is a deep copy,
not a shallow copy as might be returned by COPY-STRUCTURE."
)
)


(defgeneric update-digest (digester thing &key &allow-other-keys)
  (:documentation "Update the internal state of DIGESTER with THING.
The exact method is determined by the type of THING."
)
)


(defgeneric produce-digest (digester &key digest digest-start)
  (:documentation "Return the hash of the data processed by
DIGESTER so far. This function does not modify the internal state
of DIGESTER.

If DIGEST is provided, the hash will be placed into DIGEST starting at
DIGEST-START.  DIGEST must be a (SIMPLE-ARRAY (UNSIGNED-BYTE 8) (*)).
An error will be signaled if there is insufficient room in DIGEST."
)
)

 

;;; the digest-defining macro

(defvar *supported-digests* nil)

(defun list-all-digests ()
  (copy-seq *supported-digests*)
)


(defun digest-supported-p (name)
  "Return T if the digest NAME is a valid digest name."
  (member name *supported-digests*)
)


(defgeneric digest-length (digest)
  (:documentation "Return the number of bytes in a digest generated by DIGEST.")
)


(defmethod digest-length (digest-name)
  (error 'unsupported-digest :name digest-name)
)


(defmethod update-digest (digester (stream stream) &key &allow-other-keys)
  (update-digest-from-stream digester stream)
)


(defmethod digest-stream (digest-name stream &key digest (digest-start 0))
  (let ((digester (make-digest digest-name)))
    (update-digest-from-stream digester stream)
    (produce-digest digester :digest digest :digest-start digest-start)
)
)


(defmacro defdigest (name &rest initargs)
  (%defdigest name initargs)
)


(defun %defdigest (name initargs)
  (let ((creation-function nil)
        (copy-function nil)
        (update-function nil)
        (finalize-function nil)
        (state-type nil)
        (digest-length nil)
        (digest-name (intern (string name) (find-package :keyword)))
)

    (loop for (arg value) in initargs
          do
          (case arg
            (:creation-function
             (if (not creation-function)
                 (setf creation-function value)
                 (error "Specified :CREATION-FUNCTION multiple times.")
)
)

            (:copy-function
             (if (not copy-function)
                 (setf copy-function value)
                 (error "Specified :COPY-FUNCTION multiple times.")
)
)

            (:update-function
             (if (not update-function)
                 (setf update-function value)
                 (error "Specified :UPDATE-FUNCTION multiple times.")
)
)

            (:finalize-function
             (if (not finalize-function)
                 (setf finalize-function value)
                 (error "Specified :FINALIZE-FUNCTION multiple times.")
)
)

            (:state-type
             (if (not state-type)
                 (setf state-type value)
                 (error "Specified :STATE-TYPE multiple times.")
)
)

            (:digest-length
             (if (not digest-length)
                 (setf digest-length value)
                 (error "Specified :DIGEST-LENGTH multiple times.")
)
)
)

          finally (if (and creation-function copy-function update-function
                           finalize-function state-type digest-length
)

                      (return (generate-digest-forms digest-name state-type
                                                     digest-length
                                                     creation-function
                                                     copy-function update-function
                                                     finalize-function
)
)

                      (error "Didn't specify all required options for DEFDIGEST")
)
)
)
)


(defun generate-digest-forms (digest-name state-type digest-length
                                          creation-function copy-function
                                          update-function finalize-function
)

  `(progn
    (pushnew ,digest-name *supported-digests*)
    (defmethod digest-length ((digest (eql ,digest-name)))
      ,digest-length
)

    (defmethod digest-length ((digest ,state-type))
      ,digest-length
)

    (defmethod make-digest ((digest-name (eql ,digest-name)))
      (,creation-function)
)

    (defmethod copy-digest ((digester ,state-type))
      (,copy-function digester)
)

    (defmethod update-digest ((digester ,state-type) (sequence vector) &key (start 0) end &allow-other-keys)
      (,update-function digester sequence
                        :start start :end (or end (length sequence))
)
)

    (defmethod produce-digest ((digester ,state-type) &key digest (digest-start 0))
      (,finalize-function (,copy-function digester) digest digest-start)
)

    (defmethod digest-file ((digest-name (eql ,digest-name)) pathname
                            &key digest (digest-start 0)
)

      (digest-file-body pathname #',creation-function #',update-function
                        #',finalize-function
                        :buffer digest :buffer-start digest-start
)
)

    (defmethod digest-sequence ((digest-name (eql ,digest-name))
                                sequence &key (start 0) end
                                digest (digest-start 0)
)

      (digest-sequence-body sequence #',creation-function
                            #',update-function #',finalize-function
                            :start start :end end
                            :buffer digest :buffer-start digest-start
)
)
)
)

Lisppaste pastes can be made by anyone at any time. Imagine a fearsomely comprehensive disclaimer of liability. Now fear, comprehensively.