(ns ^{:author "Miki Tebeka " :doc "Message digest algorithms for Clojure"} digest (:require [clojure.string :refer [join lower-case split]]) (:import (java.io File FileInputStream InputStream) (java.security MessageDigest Provider Security) (java.util Arrays))) ; Default buffer size for reading (def ^:dynamic *buffer-size* 1024) (defn- read-some "Read some data from reader. Return [data size] if there's more to read, otherwise nil." [^InputStream reader] (let [^bytes buffer (make-array Byte/TYPE *buffer-size*) size (.read reader buffer)] (when (pos? size) (if (= size *buffer-size*) buffer (Arrays/copyOf buffer size))))) (defn- byte-seq "Return a sequence of [data size] from reader." [^InputStream reader] (take-while some? (repeatedly (partial read-some reader)))) (defn- signature "Get signature (string) of digest." [^MessageDigest algorithm] (let [size (* 2 (.getDigestLength algorithm)) sig (.toString (BigInteger. 1 (.digest algorithm)) 16) padding (join (repeat (- size (count sig)) "0"))] (str padding sig))) (defprotocol Digestible (-digest [message algorithm])) (extend-protocol Digestible (class (make-array Byte/TYPE 0)) (-digest [message algorithm] (-digest [message] algorithm)) java.util.Collection ;; Code "borrowed" from ;; * http://www.holygoat.co.uk/blog/entry/2009-03-26-1 ;; * http://www.rgagnon.com/javadetails/java-0416.html (-digest [message algorithm] (let [^MessageDigest algo (MessageDigest/getInstance algorithm)] (.reset algo) (doseq [^bytes b message] (.update algo b)) (signature algo))) String (-digest [message algorithm] (-digest [(.getBytes message)] algorithm)) InputStream (-digest [reader algorithm] (-digest (byte-seq reader) algorithm)) File (-digest [file algorithm] (with-open [f (FileInputStream. file)] (-digest f algorithm))) nil (-digest [message algorithm] nil)) (defn digest "Returns digest for message with given algorithm." [algorithm message] (-digest message algorithm)) (defn algorithms "List support digest algorithms." [] (let [providers (vec (Security/getProviders)) names (mapcat (fn [^Provider p] (enumeration-seq (.keys p))) providers) digest-names (filter #(re-find #"MessageDigest\.[A-Z0-9-]+$" %) names)] (set (map #(last (split % #"\.")) digest-names)))) (defn create-fn! [algorithm-name] (let [update-meta (fn [meta] (assoc meta :doc (str "Encode the given message with the " algorithm-name " algorithm.") :arglists '([message])))] (-> (intern 'digest (symbol (lower-case algorithm-name)) (partial digest algorithm-name)) (alter-meta! update-meta)))) (defn- create-fns "Create utility function for each digest algorithms. For example will create an md5 function for MD5 algorithm." [] (doseq [algorithm (algorithms)] (create-fn! algorithm))) ; Create utility functions such as md5, sha-256 ... (create-fns)