Skip to content

Instantly share code, notes, and snippets.

@mchampine
Created Mar 13, 2011
Embed
What would you like to do?
Strong password hasher/verifier for Clojure - incorporating salt and iterations
(ns pwprot
(:import (java.security MessageDigest Security)))
;;generate n random chars
(defn gensalt [n]
(let [charseq (map char (concat
(range 48 58) ; 0-9
(range 97 123)))] ; 0-z
(apply str
(take n
(repeatedly #(rand-nth charseq))))))
;;core digesting algorithm. Hash salt+password then iterate on result
(defn digester [hasher salt pw-clear iter]
(letfn [(hashme [hv] ;;iterate to increase work factor
(letfn [(oneround [hv]
(do (.reset hasher)
(.digest hasher hv)))]
(nth (iterate oneround hv) iter)))]
(.reset hasher)
(.update hasher (.getBytes salt))
(.update hasher (.getBytes pw-clear))
(hashme (.digest hasher))))
;; convert byte array to base36 chars
(defn b36 [hbytes] (.toString (BigInteger. 1 hbytes) 36))
;; generate password protect & verify functions
(defn pwfuncs
"Returns a map of two complimentary functions:
:digest - generates a salted iterated hash from a password.
:verify - tests a password against the digest generated by :digest
Arguments
hashalg - any legal java digest function, e.g. 'SHA-256' or 'MD5'
- also SHA-1, SHA, SHA-384, SHA-512, SHA1, MD2
saltlen - chars of salt, used to inhibit dictionary attacks
iterations - number of times the hash is iterated"
[hashalg saltlen iterations]
;;Java hash algorithm
(def jhash (MessageDigest/getInstance hashalg))
;;map of 2 complimentary functions to digest and verify passwords
;;this map is the return value of pwfuncs
{:digest (fn [pw-clear]
(let [salt (gensalt saltlen)
hashout (digester jhash salt pw-clear iterations)]
(str salt (b36 hashout))))
:verify (fn [pw-clear pw-protected]
(let [salt (apply str (take saltlen pw-protected))
hashout (digester jhash salt pw-clear iterations)]
(= pw-protected
(str salt (b36 hashout)))))}))
;;test SHA-256 with 16 bytes salt and 10k iterations
(def strongPWHasher (pwfuncs "SHA-256" 16 10000))
((strongPWHasher :verify) "orig" ((strongPWHasher :digest) "orig")) ;true
((strongPWHasher :verify) "fake" ((strongPWHasher :digest) "orig")) ;false
;;convenience functions for strongPWHasher digest and verify
(def protectPassword (strongPWHasher :digest))
(def verifyPassword (strongPWHasher :verify))
;;test convenience functions
(def stored-pw-digest (protectPassword "squeamish"))
(verifyPassword "squeamish" stored-pw-digest) ;true
(verifyPassword "guess" stored-pw-digest) ;false
;; other examples
;;(def weakPWHasher (pwfuncs "MD5" 0 0))
;;(def protectPasswordWeak (weakPWHasher :digest))
;;(def verifyPasswordWeak (weakPWHasher :verify))
;;(def veryStrongPWHasher (pwfuncs "SHA-512" 32 100000))
;;(def protectPasswordVS (veryStrongPWHasher :digest))
;;(def verifyPasswordVS (veryStrongPWHasher :verify))
@mchampine
Copy link
Author

mchampine commented Mar 13, 2011

Unadorned password hashes are ripe for dictionary attacks. By adding salt and iterations, the stored password digest value is substantially hardened from attack. Salt multiplies the storage required by a precomputed list of hashes. Enough salt makes it impractical. Iterations multiplies the work factor to generate or test a password, making brute force guessing more expensive. Mechanisms to enforce minimum password length/complexity, and to inhibit password guessing are still advised.

This is a first cut. Comments to correct flaws or Clojure programming style are welcome.

Edit1: converted to use letfn instead of nested defn.
Edit 2: pulled out gensalt and digester into independent functions

@boxxxie
Copy link

boxxxie commented Dec 24, 2012

thanks for writing this code. it would be nice for it to be integrated into a password or user management repo, if it isn't alrerady.

@bitemyapp
Copy link

bitemyapp commented Feb 18, 2013

No one should be using this, this is awful.

http://codahale.com/how-to-safely-store-a-password/

If you need to store hashes of passwords for authentication, use bcrypt, scrypt, or pbkdf2.

NEVER IMPLEMENT YOUR OWN CRYPTO

@mchampine
Copy link
Author

mchampine commented Mar 5, 2013

@Boxxie: Thanks. Given bcrypt, I'm guessing it's not too useful, but it was an interesting exercise.

@bitemyapp: The article you point to claims the overriding virtue of bcrypt is its slowness. I include an iterations param which lets you choose the work factor and therefore control the tradeoff between verification overhead and resistance to brute force guessing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment