Coverage report: /home/ellis/comp/ext/ironclad/src/kdf/password-hash.lisp

KindCoveredAll%
expression383 3.6
branch00nil
Key
Not instrumented
Conditionalized out
Executed
Not executed
 
Both branches taken
One branch taken
Neither branch taken
1
 ;;;; password-hash.lisp
2
 (in-package :crypto)
3
 
4
 (defun make-random-salt (&optional (size 16))
5
   "Generate a byte vector of SIZE (default 16) random bytes, suitable
6
 for use as a password salt."
7
   (random-data size))
8
 
9
 (defun pbkdf2-hash-password (password &key (salt (make-random-salt))
10
                                            (digest 'sha256)
11
                                            (iterations 1000))
12
   "Given a PASSWORD as a byte vector, a SALT as a byte
13
 vector (MAKE-RANDOM-SALT is called to generate a random salt if none
14
 is provided), a digest function (SHA256 by default), and a number of
15
 iterations (1000), returns the PBKDF2-derived hash of the
16
 password (byte vector) as the first value, and the SALT (byte vector)
17
 as the second value."
18
   (values (pbkdf2-derive-key digest password salt iterations (digest-length digest))
19
           salt))
20
 
21
 (defun pbkdf2-hash-password-to-combined-string (password &key
22
                                                 (salt (make-random-salt))
23
                                                 (digest 'sha256)
24
                                                 (iterations 1000))
25
   "Given a PASSWORD byte vector, a SALT as a byte vector (MAKE-RANDOM-SALT
26
 is called to generate a random salt if none is provided), a digest
27
 function (SHA256 by default), and a number of iterations (1000),
28
 returns the salt and PBKDF2-derived hash of the password encoded in a
29
 single ASCII string, suitable for use with PBKDF2-CHECK-PASSWORD."
30
   (with-standard-io-syntax
31
     (format nil "PBKDF2$~a:~a$~a$~a" digest iterations
32
             (byte-array-to-hex-string salt)
33
             (byte-array-to-hex-string
34
              (pbkdf2-hash-password password :iterations iterations
35
                                             :salt salt :digest digest)))))
36
 
37
 (defun pbkdf2-check-password (password combined-salt-and-digest)
38
   "Given a PASSWORD byte vector and a combined salt and digest string
39
 produced by PBKDF2-HASH-PASSWORD-TO-COMBINED-STRING, checks whether
40
 the password is valid."
41
   ;; can we have a dependency on regular expressions, please?
42
   (let* ((positions (loop with start = 0 repeat 3 collect
43
                          (setf start (position #\$ combined-salt-and-digest
44
                                                :start (1+ start)))))
45
          (digest-separator-position
46
           (position #\: combined-salt-and-digest :start (first positions))))
47
     (constant-time-equal
48
      (pbkdf2-hash-password
49
       password
50
       :digest (find-symbol (subseq combined-salt-and-digest
51
                                    (1+ (first positions))
52
                                    digest-separator-position)
53
                            '#:ironclad)
54
       :iterations (parse-integer combined-salt-and-digest
55
                                  :start (1+ digest-separator-position)
56
                                  :end (second positions))
57
       :salt (hex-string-to-byte-array combined-salt-and-digest
58
                                       :start (1+ (second positions))
59
                                       :end (third positions)))
60
      (hex-string-to-byte-array combined-salt-and-digest
61
                                :start (1+ (third positions))))))