Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add Scrypt key derivation function.

  • Loading branch information...
commit 61afbeb39f9c0598695a9618b8a942335ac7700c 1 parent 8773097
@howeyc authored
View
27 doc/ironclad-doc.txt
@@ -425,19 +425,23 @@ passed to " @make-hmac " when " 'hmac' " was constructed.")
@insufficient-buffer-space " error will be signaled if there is
insufficient space in " 'buffer' ".")
-(:h3 "PBKDF")
-
-(:p "Ironclad comes with the basic PBKDF1 and PBKDF2 algorithms, as
-well as convenience functions for using them to store passwords.")
+((:h2 id "kdfs") "Key derivation functions")
(:describe :function (ironclad:derive-key digest))
(:p "Given a key derivation function object (produced by " `make-kdf`
-"), a password and salt (both must be of type " `(SIMPLE-ARRAY
+ or `make-scrypt-kdf`"), a password and salt (both must be of type " `(SIMPLE-ARRAY
(UNSIGNED-BYTE 8) (*))` "), and number of digest iterations, returns
the password digest as a byte array of length " 'key-length' ".")
-(:describe :function (ironclad:make-kdf kind))
+(:p "Scrypt ignores " 'iteration-count' "parameter.")
+
+(:h3 "PBKDF")
+
+(:p "Ironclad comes with the basic PBKDF1 and PBKDF2 algorithms, as
+well as convenience functions for using them to store passwords.")
+
+(:describe :function (ironclad:make-kdf kdf))
(:p "Returns a key derivation function instance (" 'kind' " must
either be " 'pbkdf1' " or " 'pbkdf2' ") that uses the given "
@@ -461,6 +465,17 @@ that encodes the given salt and PBKDF2 algorithm parameters.")
produced by " `pbkdf2-hash-password-to-combined-string` ", checks whether
the password is valid.")
+(:h3 "Scrypt")
+
+(:p "Ironclad comes with and implementation of the scrypt key
+derivation function as defined by Colin Percival's " (:url
+"http://www.tarsnap.com/scrypt.html"
+"The scrypt key derivation function."))
+
+(:describe :function (ironclad:make-scrypt-kdf kdf))
+
+(:p "Returns an scrypt key derivation function instance.")
+
(:h3 "CMACs")
(:p "Instances of CMACs are constructed by specifying a secret key and a
View
2  ironclad.asd
@@ -49,6 +49,7 @@
(:file "octet-stream" :depends-on ("common"))
(:file "padding" :depends-on ("common"))
(:file "pkcs5" :depends-on ("common"))
+ (:file "scrypt" :depends-on ("pkcs5"))
(:file "password-hash" :depends-on ("pkcs5"))
(:file "math" :depends-on ("prng" "public-key"))
(:module "sbcl-opt"
@@ -226,6 +227,7 @@
(:file "digests")
(:file "padding")
(:file "pkcs5")
+ (:file "scrypt")
(:file "ironclad")
(:file "prng")
;; test vectors
View
9 src/conditions.lisp
@@ -64,6 +64,15 @@ for a particular mode of operation but not supplied."))
(digest condition))))
(:documentation "Signaled when an invalid digest name is provided to MAKE-DIGEST."))
+(define-condition unsupported-scrypt-cost-factors (ironclad-error)
+ ((N :initarg :N :reader cost-N)
+ (r :initarg :r :reader cost-r)
+ (p :initarg :p :reader cost-p))
+ (:report (lambda (condition stream)
+ (format stream "Scrypt cost factors not supported. N=~A must be a power of two and (r=~A * p=~A) <= 2 power of 30."
@froydnj
froydnj added a note

Just say "2^30" instead of "2 power of 30".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ (cost-N condition) (cost-r condition) (cost-p condition))))
+ (:documentation "Signaled when a invalid cost factors are provided to MAKE-SCRYPT-KDF."))
+
(define-condition insufficient-buffer-space (ironclad-error)
((buffer :initarg :buffer :reader insufficient-buffer-space-buffer)
(start :initarg :start :reader insufficient-buffer-space-start)
View
5 src/package.lisp
@@ -33,8 +33,8 @@
#:ecb #:cbc #:ctr #:ofb #:cfb #:stream
;; KDFs
- #:pbkdf1 #:pbkdf2
- #:make-kdf #:derive-key
+ #:pbkdf1 #:pbkdf2 #:scryptkdf
+ #:make-kdf #:make-scrypt-kdf #:derive-key
;; KDF convenience functions
#:make-random-salt #:pbkdf2-hash-password
@@ -65,6 +65,7 @@
#:ironclad-error #:initialization-vector-not-supplied
#:invalid-initialization-vector #:invalid-key-length
#:unsupported-cipher #:unsupported-mode #:unsupported-digest
+ #:unsupported-scrypt-cost-factors
#:insufficient-buffer-space #:invalid-padding
#:key-not-supplied
View
92 src/scrypt.lisp
@@ -0,0 +1,92 @@
+;;;; -*- mode: lisp; indent-tabs-mode: nil -*-
+(in-package :crypto)
+
+
+;;; scrypt from Colin Percival's
+;;; "Stronger Key Derivation via Sequential Memory-Hard Functions"
+;;; presented at BSDCan'09, May 2009.
+;;; http://www.tarsnap.com/scrypt.html
+
+(defclass scryptkdf ()
+ ((N :accessor scrypt-kdf-N
+ :initarg :N
+ :initform 16384)
@froydnj
froydnj added a note

Minor style nits on this class:

  • I think these :INITFORMs are unnecessary, given that you always pass the appropriate :INITARGs;
  • The :ACCESSORs should be :READERs, given that you never set these;
  • I think the class reads slightly better as SCRYPT-KDF, but I'm not going to complain if you'd rather keep it the way it is.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ (r :accessor scrypt-kdf-r
+ :initarg :r
+ :initform 8)
+ (p :accessor scrypt-kdf-p
+ :initarg :p
+ :initform 1)))
+
+(defmacro salsa-vector-4mix (x i4 i8 i12 i0)
+ `(setf (aref ,x ,i4) (ldb (byte 32 0) (logxor (aref ,x ,i4) (rol32 (mod32+ (aref ,x ,i0) (aref ,x ,i12)) 7)))
+ (aref ,x ,i8) (ldb (byte 32 0) (logxor (aref ,x ,i8) (rol32 (mod32+ (aref ,x ,i4) (aref ,x ,i0)) 9)))
+ (aref ,x ,i12) (ldb (byte 32 0) (logxor (aref ,x ,i12) (rol32 (mod32+ (aref ,x ,i8) (aref ,x ,i4)) 13)))
+ (aref ,x ,i0) (ldb (byte 32 0) (logxor (aref ,x ,i0) (rol32 (mod32+ (aref ,x ,i12) (aref ,x ,i8)) 18)))))
+
+(defun scrypt-vector-salsa (b)
+ (let ((x (make-array 16 :element-type '(unsigned-byte 32)))
+ (w (make-array 16 :element-type '(unsigned-byte 32))))
@froydnj
froydnj added a note

Please declare these two arrays DYNAMIC-EXTENT.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ (declare (type (simple-array (unsigned-byte 32) (16)) x w))
+ (fill-block-ub8-le x b 0)
+ (replace w x)
+
+ (loop repeat 4 do
+ (salsa-vector-4mix x 4 8 12 0)
+ (salsa-vector-4mix x 9 13 1 5)
+ (salsa-vector-4mix x 14 2 6 10)
+ (salsa-vector-4mix x 3 7 11 15)
+ (salsa-vector-4mix x 1 2 3 0)
+ (salsa-vector-4mix x 6 7 4 5)
+ (salsa-vector-4mix x 11 8 9 10)
+ (salsa-vector-4mix x 12 13 14 15))
+
+ (dotimes (i 16)
+ (setf (nibbles:ub32ref/le b (* i 4)) (mod32+ (aref x i) (aref w i))))))
+
+(defun block-mix (b xy xy-start r)
+ (let ((xs (make-array 64 :element-type '(unsigned-byte 8))))
@froydnj
froydnj added a note

Please make this DYNAMIC-EXTENT and declare its type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ (replace xs b :start2 (* 64 (1- (* 2 r))) :end1 64)
+ (dotimes (i (* 2 r))
+ (xor-block 64 xs b (* i 64) xs 0)
+ (scrypt-vector-salsa xs)
+ (replace xy xs :start1 (+ xy-start (* i 64)) :end2 64))
+ (dotimes (i r)
+ (replace b xy :start1 (* i 64) :end1 (+ 64 (* i 64)) :start2 (+ xy-start (* 64 2 i))))
+ (dotimes (i r)
+ (replace b xy :start1 (* 64 (+ i r)) :end1 (+ (* 64 (+ i r)) 64) :start2 (+ xy-start (* 64 (1+ (* i 2))))))))
+
+(defun smix (b b-start r N v xy)
+ (let ((x xy)
+ (xy-start (* 128 r)))
+ (replace x b :end1 (* 128 r) :start2 b-start)
@froydnj
froydnj added a note

Can we use XY-START everywhere (* 128 R) appears in this function? CL compilers are generally not that sophisticated about optimizing repeated expressions (as opposed to C compilers, say). Doing the same sort of thing in BLOCK-MIX would be helpful, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ (dotimes (i N)
+ (replace v x :start1 (* i 128 r) :end2 (* 128 r))
+ (block-mix x xy xy-start r))
+ (dotimes (i N)
+ (let ((j (ldb (byte 32 0) (logand (nibbles:ub64ref/le x (* (1- (* 2 r)) 64)) (1- N)))))
+ (xor-block (* 128 r) x v (* j 128 r) x 0)
+ (block-mix x xy xy-start r)))
+ (replace b x :start1 b-start :end1 (* 128 r))))
+
+(defmethod derive-key ((kdf scryptkdf) passphrase salt iteration-count key-length)
+ (declare (ignore iteration-count))
+ (let ((xy (make-array (* 256 (scrypt-kdf-r kdf)) :element-type '(unsigned-byte 8)))
+ (v (make-array (* 128 (scrypt-kdf-r kdf) (scrypt-kdf-N kdf)) :element-type '(unsigned-byte 8)))
+ (b (derive-key (make-kdf 'PBKDF2 :digest 'SHA256) passphrase salt 1 (* (scrypt-kdf-p kdf) 128 (scrypt-kdf-r kdf)))))
@froydnj
froydnj added a note

Minor efficiency point: you could create the pbkdf2 instance once, use it here, and then REINITIALIZE-INSTANCE on it before using it below.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ (dotimes (i (scrypt-kdf-p kdf))
+ (smix b (* i 128 (scrypt-kdf-r kdf)) (scrypt-kdf-r kdf) (scrypt-kdf-N kdf) v xy))
+ (derive-key (make-kdf 'PBKDF2 :digest 'SHA256) passphrase b 1 key-length)))
+
+(defun make-scrypt-kdf (&optional (N 16384 N-supplied-p) (r 8 r-supplied-p) (p 1 p-supplied-p))
@froydnj
froydnj added a note

A couple of comments about this function. I think it would be acceptable to add N, r, and p keyword args to MAKE-KDF, even if those keywords aren't going to be used for PBKDF-esque KDFs. There should be a little bit of documentation describing the applicability of particular keyword arguments.

Also, I think the -supplied-p variables are unnecessary; removing them also means that you sanity check your default arguments, which is a good thing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ "N is a CPU/memory cost parameter, and must be a power of two greater than 1.
+ r and p must satisfy (< (* r p) (expt 2 30)). If the parameters do not satisfy
+ the limits, that results in an unsupported-scrypt-costs error condition.
+
+ The recommended paramters for interactive logins as of 2009 are:
+ N=16384, r=8, p=1. They should be increased as memory latency and CPU parallelism
+ increases."
+ (when (or (and N-supplied-p (or (<= N 1) (not (zerop (logand N (1- N))))))
+ (and (or r-supplied-p p-supplied-p) (>= (* r p) (expt 2 30))))
+ (error 'unsupported-scrypt-cost-factors :N N :r r :p p))
+ (make-instance 'scryptkdf :N N :r r :p p))
View
23 testing/test-vectors/scrypt.lisp
@@ -0,0 +1,23 @@
+;;;; -*- mode: lisp; indent-tabs-mode: nil -*-
+(in-package :crypto-tests)
+
+;;; Test vectors based on those defined in the Go implementation.
+;;;
+;;; http://github.com/dchest/scrypt
+
+(defvar *scrypt-password*
+ (coerce #(112 97 115 115 119 111 114 100)
+ '(vector (unsigned-byte 8))))
+(defvar *scrypt-salt*
+ (coerce #(115 97 108 116)
+ '(vector (unsigned-byte 8))))
+
+(defvar *scrypt-key*
+ (coerce #(116 87 49 175 68 132 243 35 150 137 105 237 162 137 174 238 0 91 89 3 172 86
+ 30 100 165 172 161 33 121 123 247 115)
+ '(vector (unsigned-byte 8))))
+
+(rtest:deftest scryptkdf
+ (run-kdf-test (crypto:make-scrypt-kdf)
+ *scrypt-password* *scrypt-salt* 1000 (length *scrypt-key*) *scrypt-key*)
+ t)

1 comment on commit 61afbeb

@froydnj

Thanks for updating the documentation, too! Don't worry about regenerating the HTML; I'll do that separately.

Please sign in to comment.
Something went wrong with that request. Please try again.