;;;  md5-old.el -- MD5 message digest algorithm

;; Copyright (C) 1998 Ray Jones

;; Author: Ray Jones, rjones@pobox.com
;; Keywords: MD5, message digest
;; Created: 1998-04-27

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, you can either send email to this
;; program's maintainer or write to: The Free Software Foundation,
;; Inc.; 675 Massachusetts Avenue; Cambridge, MA 02139, USA.

;;; Commentary:

;; this is a slower, more clear, version of md5.el.  it's based on md5-old.el

;;; Code:
(require 'cl)

(defun md5 (string)
  "return the md5 hash of a string, as a 128 bit string"
  (let* ((length (length string))
	 ;; md5 requires the message be padded to a length of 512*k +
	 ;; 64 (bits).  confusion source: we're working with bytes.
	 ;; padding is always done.
	 ;; 512 bits = 64 bytes, 64 bits = 8 bytes
	 (next-512 (+ 64 (logand (+ length 8) (lognot 63))))
	 (pad-bytes (- next-512 length 8))
	 (pad-string (make-string pad-bytes 0))
	 (len-string (make-string 8 0)))
    ;; message is constructed as:
    ;; original-message | pad | length-in-bits
    ;; pad is 10000... (bitwise)
    ;; length-in-bits is length before padding, and is 64 bits long

    ;; fill in the single bit of the pad
    (aset pad-string 0 (ash 1 7))

    ;; there's a slim chance of overflow when multiplying the length
    ;; by 8 to get the length in bits.  to avoid this, do some
    ;; slightly hairier math when writing the length into len-string.
    ;; also, it has to be LSB-first.  be still my aching brain.

    ;; LSB sucks.

    ;; only do the first 4 bytes, even though supposedly there are 8.
    ;; 32 bit emacsen think that (ash 40 -37) => 1
    ;; (supposed to be fixed in future releases)
    (dotimes (idx 4)
      (aset len-string idx (logand ?\xff
				   (ash length (- 3 (* idx 8))))))
    
    (md5-vector
     (md5-string-to-32bit-vec
      (concat string pad-string len-string)))))

(defun md5-string-to-32bit-vec (string)
  ;; emacs doesn't actually have 32 bits, in most implementations.
  ;; 32 bit numbers are represented as a pair of 16 bit numbers

  ;; 4 chars per 32 bit number, in LSB-first!
  (let* ((veclen (/ (length string) 4))
	 (vec (make-vector veclen nil))
	 (stridx 0))
    (dotimes (vecidx veclen)
      ;; MD5 integers are (hi . lo) 16 bit words
      (aset vec vecidx (cons (+ (ash (aref string (+ stridx 3)) 8)
				(aref string (+ stridx 2)))
			     (+ (ash (aref string (+ stridx 1)) 8)
				(aref string (+ stridx 0)))))
      (incf stridx 4))

    vec))

(defsubst md5-f2 (x y z)
  (logior (logand x y)
	  (logand (lognot x)
		  z)))

(defsubst md5-g2 (x y z)
  (logior (logand x z)
	  (logand y (lognot z))))

(defsubst md5-h2 (x y z)
  (logxor x y z))

(defsubst md5-i2 (x y z)
  (logxor y
	  (logior x
		  ;; this is normally a lognot, but that would set
		  ;; high bits, and there's no logand to clear them.
		  (logxor z #xffff))))

(defsubst md5-f (x y z)
  (cons (md5-f2 (car x) (car y) (car z))
	(md5-f2 (cdr x) (cdr y) (cdr z))))

(defsubst md5-g (x y z)
  (cons (md5-g2 (car x) (car y) (car z))
	(md5-g2 (cdr x) (cdr y) (cdr z))))

(defsubst md5-h (x y z)
  (cons (md5-h2 (car x) (car y) (car z))
	(md5-h2 (cdr x) (cdr y) (cdr z))))

(defsubst md5-i (x y z)
  (cons (md5-i2 (car x) (car y) (car z))
	(md5-i2 (cdr x) (cdr y) (cdr z))))

(defsubst md5<<< (val shift)
  "circular shift md5 32 bit int VAL by SHIFT bits"
  (let ((a (car val))
	(b (cdr val)))

    ;; shifts greater than 16 need to be handled by a swap, then a
    ;; smaller shift
    (when (> shift 16)
      (rotatef a b)
      (decf shift 16))

    (cons (logand #xffff (logior (ash a shift) (ash b (- shift 16))))
	  (logand #xffff (logior (ash b shift) (ash a (- shift 16)))))))

(defsubst md5+ (&rest args)
  ;; enough room to just add without carry checks
  (let* ((lo (apply #'+ (mapcar #'cdr args)))
	 (hi (+ (ash lo -16) (apply #'+ (mapcar #'car args)))))
    (cons (logand #xffff hi)
	  (logand #xffff lo))))

;; array of values for i=[1..64] => floor(2^32 * abs(sin(i)))
(defconst md5-t

  [(#xd76a . #xa478)
   (#xe8c7 . #xb756)
   (#x2420 . #x70db)
   (#xc1bd . #xceee)
   (#xf57c . #x0faf)
   (#x4787 . #xc62a)
   (#xa830 . #x4613)
   (#xfd46 . #x9501)
   (#x6980 . #x98d8)
   (#x8b44 . #xf7af)
   (#xffff . #x5bb1)
   (#x895c . #xd7be)
   (#x6b90 . #x1122)
   (#xfd98 . #x7193)
   (#xa679 . #x438e)
   (#x49b4 . #x0821)

   (#xf61e . #x2562)
   (#xc040 . #xb340)
   (#x265e . #x5a51)
   (#xe9b6 . #xc7aa)
   (#xd62f . #x105d)
   (#x0244 . #x1453)
   (#xd8a1 . #xe681)
   (#xe7d3 . #xfbc8)
   (#x21e1 . #xcde6)
   (#xc337 . #x07d6)
   (#xf4d5 . #x0d87)
   (#x455a . #x14ed)
   (#xa9e3 . #xe905)
   (#xfcef . #xa3f8)
   (#x676f . #x02d9)
   (#x8d2a . #x4c8a)

   (#xfffa . #x3942)
   (#x8771 . #xf681)
   (#x6d9d . #x6122)
   (#xfde5 . #x380c)
   (#xa4be . #xea44)
   (#x4bde . #xcfa9)
   (#xf6bb . #x4b60)
   (#xbebf . #xbc70)
   (#x289b . #x7ec6)
   (#xeaa1 . #x27fa)
   (#xd4ef . #x3085)
   (#x0488 . #x1d05)
   (#xd9d4 . #xd039)
   (#xe6db . #x99e5)
   (#x1fa2 . #x7cf8)
   (#xc4ac . #x5665)

   (#xf429 . #x2244)
   (#x432a . #xff97)
   (#xab94 . #x23a7)
   (#xfc93 . #xa039)
   (#x655b . #x59c3)
   (#x8f0c . #xcc92)
   (#xffef . #xf47d)
   (#x8584 . #x5dd1)
   (#x6fa8 . #x7e4f)
   (#xfe2c . #xe6e0)
   (#xa301 . #x4314)
   (#x4e08 . #x11a1)
   (#xf753 . #x7e82)
   (#xbd3a . #xf235)
   (#x2ad7 . #xd2bb)
   (#xeb86 . #xd391)])

(eval-and-compile
  (defun md5-rewrite (fun w x y z vec-idx shift)
    "helper function for md5-vector, below.  ugly coding practice,
having a macro-rewriter elsewhere, but the indentation was getting a
bit out of control.
NB: vec, v-offset, and t-idx below must be defined where the macro is
called!" 
    `(setq ,w (md5+ ,x
		    (md5<<< (md5+ ,w
				  ,(list fun x y z)
				  (aref vec (+ v-offset ,vec-idx))
				  (aref md5-t t-idx))
			    ,shift)))))


(defun md5-vector (vec)
  ;; initialize the chaining variables
  (let ((a (cons #x6745 #x2301))
	(b (cons #xefcd #xab89))
	(c (cons #x98ba #xdcfe))
	(d (cons #x1032 #x5476))
	(v-offset 0))

    (dotimes (count (/ (length vec) 16))
      (let ((AA a) (BB b) (CC c) (DD d)
	    (t-idx 0))
	(macrolet
	    ((f (v1 v2 v3 v4 v-idx shift)
		`(progn
		   ,(md5-rewrite 'md5-f v1 v2 v3 v4 v-idx shift)
		   (incf t-idx))))

	  (f a b c d  0  7) (f d a b c  1 12) (f c d a b  2 17) (f b c d a  3 22)
	  (f a b c d  4  7) (f d a b c  5 12) (f c d a b  6 17) (f b c d a  7 22)
	  (f a b c d  8  7) (f d a b c  9 12) (f c d a b 10 17) (f b c d a 11 22)
	  (f a b c d 12  7) (f d a b c 13 12) (f c d a b 14 17) (f b c d a 15 22))

	(macrolet
	    ((g (v1 v2 v3 v4 v-idx shift)
		`(progn
		   ,(md5-rewrite 'md5-g v1 v2 v3 v4 v-idx shift)
		   (incf t-idx))))

	  (g a b c d  1  5) (g d a b c  6  9) (g c d a b 11 14) (g b c d a  0 20)
	  (g a b c d  5  5) (g d a b c 10  9) (g c d a b 15 14) (g b c d a  4 20)
	  (g a b c d  9  5) (g d a b c 14  9) (g c d a b  3 14) (g b c d a  8 20)
	  (g a b c d 13  5) (g d a b c  2  9) (g c d a b  7 14) (g b c d a 12 20))

	(macrolet
	    ((h (v1 v2 v3 v4 v-idx shift)
		`(progn
		   ,(md5-rewrite 'md5-h v1 v2 v3 v4 v-idx shift)
		   (incf t-idx))))

	  (h a b c d  5  4) (h d a b c  8 11) (h c d a b 11 16) (h b c d a 14 23)
	  (h a b c d  1  4) (h d a b c  4 11) (h c d a b  7 16) (h b c d a 10 23)
	  (h a b c d 13  4) (h d a b c  0 11) (h c d a b  3 16) (h b c d a  6 23)
	  (h a b c d  9  4) (h d a b c 12 11) (h c d a b 15 16) (h b c d a  2 23))

	(macrolet
	    ((i (v1 v2 v3 v4 v-idx shift)
		`(progn
		   ,(md5-rewrite `md5-i v1 v2 v3 v4 v-idx shift)
		   (incf t-idx))))

	  (i a b c d  0  6) (i d a b c  7 10) (i c d a b 14 15) (i b c d a  5 21)
	  (i a b c d 12  6) (i d a b c  3 10) (i c d a b 10 15) (i b c d a  1 21)
	  (i a b c d  8  6) (i d a b c 15 10) (i c d a b  6 15) (i b c d a 13 21)
	  (i a b c d  4  6) (i d a b c 11 10) (i c d a b  2 15) (i b c d a  9 21))

	(setq a (md5+ AA a)
	      b (md5+ BB b)
	      c (md5+ CC c)
	      d (md5+ DD d)))

      (incf v-offset 16))

    ;; swap back from LSB-first.  i feel ill.
    (mapconcat #'(lambda (x) (format "%02x%02x" (logand #xff x) (ash x -8)))
	       (list (cdr a) (car a)
		     (cdr b) (car b)
		     (cdr c) (car c)
		     (cdr d) (car d))
	       "")))

(provide 'md5)
