;;; -*- Mode:LISP; Package:USER; Base:10; Readtable:CL -*-

(defvar *chars-in-key* 8.)

(defvar *salted-expansion-function*)

(defun crypt (password salt)
  (let ((key-bits (key->bit-string password))
	(salt-string (pad-string salt 2.)))
    (with-salted-expansion-function salt-string
      #'(lambda ()
	  (let ((output-bits
		  (concat-bit-strings
		    (des (make-bit-string 64.) key-bits 25.)
		    (make-bit-string 2.))))
	    (do ((output-string salt-string
				(string-append output-string
					       (six-bit->ascii
						 (ldb-from-bit-string (byte 6 (* i 6)) output-bits))))
		 (i 0 (1+ i)))
		((= i 11.) (when (zerop (elt output-string 1))
			     (setf (elt output-string 1)
				   (elt output-string 0)))
		 output-string)))))))

(defun key->bit-string (key)
  (let ((padded-key (pad-key key)))
    (do ((i 0 (1+ i))
	 (result-bit-string
	   (make-bit-string 0)
	   (concat-bit-strings
	     result-bit-string
	     (key-char->des-format (elt padded-key i)))))
	((= i *chars-in-key*) result-bit-string))))

(defun pad-key (key)
  (pad-string key *chars-in-key*))

(defun pad-string (string pad)
  (substring (string-append string (make-string pad)) 0 pad))

(defun key-char->des-format (char)
  (let ((char-bits (%char->bit-string char)))
    (concat-bit-strings
      (mirror-bit-string
	(ldb-from-bit-string (byte 7 0) char-bits))
      (ldb-from-bit-string (byte 1 7) char-bits))))

(defun with-salted-expansion-function (salt receiver)
  (let ((*salted-expansion-function* (copy-seq expansion-function)))
    (dotimes (i 2)
      (let ((six-bit (ascii->six-bit (elt salt i))))
	(dotimes (j 6)
	  (when (not (zerop (elt six-bit j)))
	    (swapf (elt *salted-expansion-function* (+ (* 6 i) j))
		   (elt *salted-expansion-function* (+ (* 6 i) j 24.)))))))
    (funcall receiver)))

(defun des (block key times)
  (multiple-value-bind (left right)
      (block->initial-halves block)
    (let ((key-schedule (generate-key-schedule key)))
      (dotimes (i times)
	(multiple-value-setq (left right)
	  (des-loop left right key-schedule)))
      (apply-permutation final-permutation (concat-bit-strings left right)))))

(defun block->initial-halves (block)
  (let ((p-block (apply-permutation initial-permutation block)))
    (values (ldb-from-bit-string (byte 32 0) p-block)
	    (ldb-from-bit-string (byte 32 32) p-block))))

(defun generate-key-schedule (key-bit-string)
  (multiple-value-bind (c0 d0)
      (key-bits->c-and-d key-bit-string)
    (let ((key-schedule (make-array 16.))
	  (c c0)
	  (d d0)
	  (shift-schedule key-shift-schedule))
      (dotimes (i 16.)
	(setq c (rotate-bit-string c (- (first shift-schedule))))
	(setq d (rotate-bit-string d (- (first shift-schedule))))
	(setq shift-schedule (rest shift-schedule))
	(setf (elt key-schedule i)
	      (apply-permutation permuted-choice-2 (concat-bit-strings c d))))
      key-schedule)))

(defun key-bits->c-and-d (key-bit-string)
  (let ((permuted-key (apply-permutation permuted-choice-1 key-bit-string)))
    (values (ldb-from-bit-string (byte 28 0) permuted-key)
	    (ldb-from-bit-string (byte 28 28) permuted-key))))

(defun des-loop (left right key-schedule)
  (dotimes (i 16)
    (multiple-value-setq (left right)
      (des-round left right (elt key-schedule i)))
;    (format t "~&~s ~s" left right)
    )
  (values right left))

(defun des-round (left right key )
  (values right (bit-xor left (des-f right key))))

(defun des-f (right key)
  (apply-permutation p-permutation
		     (pass-through-s-boxes
		       (expand-block-half right key))))

(defun expand-block-half (block-half key)
  (bit-xor key (apply-permutation *salted-expansion-function* block-half)))

(defun pass-through-s-boxes (expanded-bit-string)
;  (terpri)
;  (dotimes (i 48.) (format t "~b" (elt expanded-bit-string i)))
  (do ((i 0 (1+ i))
       (sbox-output (make-bit-string 0.)
		    (concat-bit-strings
		      sbox-output
		      (sbox-ref (elt s-boxes i)
				(ldb-from-bit-string (byte 6 (* i 6)) expanded-bit-string)))))
      ((= i 8.) sbox-output)))

(defun sbox-ref (sbox bit-string)
  (elt sbox
       (bit-string->fixnum
	 (concat-bit-strings
	   (mirror-bit-string
	     (ldb-from-bit-string (byte 4 1) bit-string))
	   (ldb-from-bit-string (byte 1 5) bit-string)
	   (ldb-from-bit-string (byte 1 0) bit-string)))))

(defun apply-permutation (p bit-string)
  (let ((dest (make-bit-string (length p))))
    (for-elements-in-vector p
      #'(lambda (index value)
	  (setf (elt dest index) (elt bit-string value))))
	dest))

(defun ascii->six-bit (c)
  ;; Snarfed from some C code.
  (when (char> c #\Z)
    (decf c 6))
  (when (char> c #\9)
    (decf c 7))
  (ldb-from-bit-string (byte 6 0) (%char->bit-string (decf c #\.))))

(defun six-bit->ascii (i)
  (let ((c (%bit-string->char
	     (concat-bit-strings
	       (mirror-bit-string i)
	       (make-bit-string 2.)))))
    ;; More C code.
    (incf c #\.)
    (when (char> c #\9) (incf c 7))
    (when (char> c #\Z) (incf c 6))
    c))

;;;; Using the above code, des takes about 37 seconds, or .03 passwords/sec

;;;; First optimization:  Shuffle S-boxes to avoid bit extraction in
;;;; s-box ref.

;(defvar *new-s-boxes* (make-array (length s-boxes)))

;(defun compute-new-s-box-value (index old-s-box new-s-box)
;  (let ((bit-string (%fixnum->bit-string index)))
;    (setf (elt new-s-box index)
;	  (elt old-s-box
;	       (bit-string->fixnum
;		 (concat-bit-strings
;		   (mirror-bit-string
;		     (ldb-from-bit-string (byte 4 1) bit-string))
;		   (ldb-from-bit-string (byte 1 5) bit-string)
;		   (ldb-from-bit-string (byte 1 0) bit-string)))))))

;(defun compute-new-s-box (old-s-box)
;  (let ((length (length old-s-box)))
;    (let ((new-s-box (make-array length)))
;      (dotimes (i length)
;	(compute-new-s-box-value i old-s-box new-s-box))
;      new-s-box)))

;(defun compute-new-s-boxes ()
;  (dotimes (i (length s-boxes))
;    (setf (elt *new-s-boxes* i) (compute-new-s-box (elt s-boxes i)))))

;(defun new-s-box-ref (s-box bit-string)
;  (elt s-box (bit-string->fixnum bit-string)))

;(defun pass-through-s-boxes (expanded-bit-string)
;  (do ((i 0 (1+ i))
;       (sbox-output (make-bit-string 0.)
;		    (concat-bit-strings
;		      sbox-output
;		      (new-s-box-ref (elt *new-s-boxes* i)
;				(ldb-from-bit-string (byte 6 (* i 6)) expanded-bit-string)))))
;      ((= i 8.) sbox-output)))

;;;; This gains a factor of about 30% now we do .04 passwords/sec
;;;; Next optimization is to make salting be an xor with a computed mask
;;;; instead of a swapping of elements in the E permutation.

;(defvar *salt-bits*)

;(defun with-salted-expansion-function (salt receiver)
;  (let ((*salt-bits*
;	  (concat-bit-strings
;	    (ascii->six-bit (elt salt 0))
;	    (ascii->six-bit (elt salt 1))
;	    (make-bit-string 12.))))
;    (funcall receiver)))

;(defun expand-block-half (block-half key)
;  (let ((bottom
;	  (concat-bit-strings
;	    (ldb-from-bit-string (byte 1. 31.) block-half)
;	    (ldb-from-bit-string (byte 5.  0.) block-half)
;	    (ldb-from-bit-string (byte 6.  3.) block-half)
;	    (ldb-from-bit-string (byte 6.  7.) block-half)
;	    (ldb-from-bit-string (byte 6. 11.) block-half)))
;	(top
;	  (concat-bit-strings
;	    (ldb-from-bit-string (byte 6. 15.) block-half)
;	    (ldb-from-bit-string (byte 6. 19.) block-half)
;	    (ldb-from-bit-string (byte 6. 23.) block-half)
;	    (ldb-from-bit-string (byte 5. 27.) block-half)
;	    (ldb-from-bit-string (byte 1.  0.) block-half))))
;      (let ((interesting-bits (bit-xor top bottom)))
;	(let ((salt-mask (bit-and *salt-bits* interesting-bits)))
;	  (bit-xor key
;		   (concat-bit-strings
;		     (bit-xor salt-mask bottom)
;		     (bit-xor salt-mask top))))))))

;;;; This didn't help.  Time to pull out the big guns.
