-
Notifications
You must be signed in to change notification settings - Fork 15
/
pbkdf2.clj
75 lines (61 loc) · 2.81 KB
/
pbkdf2.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
(ns crypto.password.pbkdf2
"Functions for encrypting passwords using the PBKDF2 algorithm, as recommended
by the NIST.
See: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf"
(:require [crypto.random :as random]
[crypto.equality :as crypto]
[clojure.string :as str])
(:import javax.crypto.SecretKeyFactory
javax.crypto.spec.PBEKeySpec
org.apache.commons.codec.binary.Base64))
(def ^:no-doc algorithm-codes
{"HMAC-SHA1" "PBKDF2WithHmacSHA1"
"HMAC-SHA256" "PBKDF2WithHmacSHA256"})
(defn- encode-str [bytes]
(String. (Base64/encodeBase64 bytes)))
(defn- encode-int [i]
(String. (Base64/encodeInteger (BigInteger/valueOf i))))
(def ^:private default-iterations-count
(Long/parseLong (System/getProperty "crypto.password.pbkdf2.default-iterations-count" "100000")))
(defn encrypt
"Encrypt a password string using the PBKDF2 algorithm. The default number of
iterations is 100,000. If a salt is not specified, 8 random bytes are
generated from a cryptographically secure source. The default algorithm is
\"HMAC-SHA1\". When running on JDK 1.8 \"HMAC-SHA256\" is also supported.
The number of iterations and salt are encoded in the output in the following
formats for HMAC-SHA1:
<iterations>$<salt>$<encrypted password>
And for all other algoritms:
<iterations>$<algorithm>$<salt>$<encrypted password>
The iterations, salt, and encrypted password are all Base64 encoded."
([raw]
(encrypt raw default-iterations-count))
([raw iterations]
(encrypt raw iterations "HMAC-SHA1"))
([raw iterations algorithm]
(encrypt raw iterations algorithm (random/bytes 8)))
([raw iterations algorithm salt]
{:pre [(contains? algorithm-codes algorithm)]}
(let [key-length 160
key-spec (PBEKeySpec. (.toCharArray raw) salt iterations key-length)
key-factory (SecretKeyFactory/getInstance (get algorithm-codes algorithm))]
(->> (.generateSecret key-factory key-spec)
(.getEncoded)
(encode-str)
(str (encode-str salt) "$")
(str (if (= algorithm "HMAC-SHA1") "" (str algorithm "$")))
(str (encode-int iterations) "$")))))
(defn- decode-str [s]
(Base64/decodeBase64 (.getBytes s)))
(defn- decode-int [s]
(int (Base64/decodeInteger (.getBytes s))))
(defn check
"Compare a raw string with a string encrypted with the [[encrypt]] function.
Returns true if the string matches, false otherwise."
[raw encrypted]
(let [parts (str/split encrypted #"\$")
iterations (decode-int (parts 0))
algorithm (if (= (count parts) 4) (parts 1) "HMAC-SHA1")
salt (decode-str (if (= algorithm "HMAC-SHA1") (parts 1) (parts 2)))
raw-encrypted (encrypt raw iterations algorithm salt)]
(crypto/eq? raw-encrypted encrypted)))