Browse files

Merge pull request #14 from eadmund/prng

Finished first version of PRNG functionality
  • Loading branch information...
2 parents 7a248e8 + aca9c6f commit 6428165a2b8a5d7065e4daa0bb21485ecef18490 @froydnj committed Apr 9, 2012
Showing with 530 additions and 7 deletions.
  1. +1 −1 .gitignore
  2. +4 −0 TODO
  3. +24 −2 doc/index.html
  4. +133 −1 doc/ironclad-doc.txt
  5. +11 −3 ironclad.asd
  6. +5 −0 src/package.lisp
  7. +125 −0 src/prng/fortuna.lisp
  8. +62 −0 src/prng/generator.lisp
  9. +114 −0 src/prng/prng.lisp
  10. +5 −0 testing/test-vectors/prng.lisp
  11. +17 −0 testing/test-vectors/prng.testvec
  12. +29 −0 testing/testfuns.lisp
View
2 .gitignore
@@ -14,4 +14,4 @@
*.fx64fsl
*.fas
*.lib
-
+*~
View
4 TODO
@@ -208,3 +208,7 @@ difference--at least not in the case where WITH-WORDS needs to check for
alignment and safety. It might be a win if there were several
different *-ENCRYPT-BLOCK functions and the high-level ones knew which
one to call, but...
+
+* add option to allow INTEGER-TO-OCTETS to output directly to a buffer
+
+This could be handy in certain internals, e.g. in GENERATE-BLOCKS.
View
26 doc/index.html
@@ -128,7 +128,7 @@
algorithm. Returns the derived hash of the password, and the original
salt, as byte vectors.</p><div class="lisp-symbol"><a name="pbkdf2-hash-password-to-combined-string"></a><tt><strong>pbkdf2-hash-password-to-combined-string</strong> <em>password</em> <em><tt>&key</tt></em> <em>salt</em> <em>digest</em> <em>iterations</em> =&gt; <em>password</em></tt><br /></div><p>Convenience function for hashing passwords using the PBKDF2
algorithm. Returns the derived hash of the password as a single string
-that encodes the given salt and PBKDF2 algorithm parameters.</p><div class="lisp-symbol"><a name="pbkdf2-check-password"></a><tt><strong>pbkdf2-check-password</strong> <em>password</em> <em>combined-salt-and-digest</em> =&gt; <em>validp</em></tt><br /></div><p>Given a <em>password</em> byte vector and a combined salt and digest string
+that encodes the given salt and PBKDF2 algorithm parameters.</p><div class="lisp-symbol"><a name="pbkdf2-check-password"></a><tt><strong>pbkdf2-check-password</strong> <em>password</em> <em>combined-salt-and-digest</em> =&gt; <em>boolean</em></tt><br /></div><p>Given a <em>password</em> byte vector and a combined salt and digest string
produced by <tt>pbkdf2-hash-password-to-combined-string</tt>, checks whether
the password is valid.</p><h3>CMACs</h3><p>Instances of CMACs are constructed by specifying a secret key and a
cipher-name.</p><div class="lisp-symbol"><a name="make-cmac"></a><tt><strong>make-cmac</strong> <em>key</em> <em>cipher-name</em> =&gt; <em>cmac</em></tt><br /></div><p>Return a CMAC instance based on the cipher <em>cipher-name</em> with
@@ -157,7 +157,29 @@
vectors; different applications may have different requirements. <a href="#sign-message" style="symbol">sign-message</a> therefore returns objects and lets the user determine
how to best format the values contained therein.</p><div class="lisp-symbol"><a name="dsa-signature"></a><tt><strong>dsa-signature</strong></tt><br /></div><p>A DSA signature object.</p><div class="lisp-symbol"><a name="make-dsa-signature"></a><tt><strong>make-dsa-signature</strong> <em>r</em> <em>s</em> =&gt; <em>signature</em></tt><br /></div><p>Returns a DSA signature with the provided <em>r</em> and <em>s</em>
values. <em>r</em> and <em>s</em> may be either integers or they may be
-20-byte octet vectors.</p><div class="lisp-symbol"><a name="dsa-signature-r"></a><tt><strong>dsa-signature-r</strong> <em>object</em> =&gt; <em>integer</em></tt><br /></div><p>Returns the <em>r</em> value of the provided DSA signature.</p><div class="lisp-symbol"><a name="dsa-signature-s"></a><tt><strong>dsa-signature-s</strong> <em>object</em> =&gt; <em>integer</em></tt><br /></div><p>Returns the <em>s</em> value of the provided DSA signature.</p><h3>Encryption and decryption</h3><div class="lisp-symbol"><a name="encrypt-message"></a><tt><strong>encrypt-message</strong> <em>key</em> <em>message</em> <em><tt>&key</tt></em> <em>start</em> <em>end</em> <em>end</em> <em>start</em> =&gt; <em>encrypted-message</em></tt><br /></div><div class="lisp-symbol"><a name="decrypt-message"></a><tt><strong>decrypt-message</strong> <em>key</em> <em>message</em> <em><tt>&key</tt></em> <em>start</em> <em>end</em> <em>start</em> =&gt; <em>decrypted-message</em></tt><br /></div><h2 id="gray-streams">Gray Streams</h2><p>Ironclad includes support for several convenient stream
+20-byte octet vectors.</p><div class="lisp-symbol"><a name="dsa-signature-r"></a><tt><strong>dsa-signature-r</strong> <em>object</em> =&gt; <em>integer</em></tt><br /></div><p>Returns the <em>r</em> value of the provided DSA signature.</p><div class="lisp-symbol"><a name="dsa-signature-s"></a><tt><strong>dsa-signature-s</strong> <em>object</em> =&gt; <em>integer</em></tt><br /></div><p>Returns the <em>s</em> value of the provided DSA signature.</p><h3>Encryption and decryption</h3><div class="lisp-symbol"><a name="encrypt-message"></a><tt><strong>encrypt-message</strong> <em>key</em> <em>message</em> <em><tt>&key</tt></em> <em>start</em> <em>end</em> <em>end</em> <em>start</em> =&gt; <em>encrypted-message</em></tt><br /></div><div class="lisp-symbol"><a name="decrypt-message"></a><tt><strong>decrypt-message</strong> <em>key</em> <em>message</em> <em><tt>&key</tt></em> <em>start</em> <em>end</em> <em>start</em> =&gt; <em>decrypted-message</em></tt><br /></div><h2 id="prng">Pseudo-random Number Generation</h2><div class="lisp-symbol"><a name="make-prng"></a><tt><strong>make-prng</strong> <em>name</em> <em><tt>&key</tt></em> <em>seed</em> <em>seed</em> <em>cipher</em> =&gt; <em>prng</em></tt><br /></div><p>Return a pseudo-random number generator.</p><p><em>name</em> denotes the style of PRNG to use. <a href="#list-all-prngs" style="symbol">list-all-prngs</a> will
+tell you the names of all supported PRNGs. Currently only FORTUNA is
+supported. <em>name</em> can be a symbol in the <tt>KEYWORD</tt> package or
+the <tt>IRONCLAD</tt> package.</p><p><em>seed</em> is a <em>seed descriptor</em>. If NIL, the PRNG will
+not be seeded (which may prevent it from generating output until it is
+seeded, depending on the generator). If <tt>:RANDOM</tt> then the PRNG
+will be seeded with the OS's cryptographically-secure PRNG. If <tt>:URANDOM</tt> then the PRNG will be seeded with the OS's
+fast-but-potentially-less-secure PRNG, if available (if not, will
+fallback to <tt>:RANDOM</tt>). If it is a pathname indicator, a seed will
+be read from the indicated file, then a new seed will be generated and
+written back to the file (over-writing the old seed). Finally, if it is
+a byte vector, it will be used to seed the PRNG.</p><div class="lisp-symbol"><a name="list-all-prngs"></a><tt><strong>list-all-prngs</strong> =&gt; <em>list</em></tt><br /></div><p>List all known PRNG types.</p><div class="lisp-symbol"><a name="random-data"></a><tt><strong>random-data</strong> <em>pseudo-random-number-generator</em> <em>num-bytes</em> =&gt; <em>bytes</em></tt><br /></div><p>Generate <em>num-bytes</em> bytes of random data from <em>pseudo-random-number-generator</em>. Updates the state of the generator.</p><div class="lisp-symbol"><a name="read-os-random-seed"></a><tt><strong>read-os-random-seed</strong> <em>prng</em> <em><tt>&optional</tt></em> <em>source</em> =&gt; <em>reseed-count</em></tt><br /></div><p>Reads an OS-provided random seed (from /dev/urandom or /dev/random
+on Unix; CryptGenRandom on Windows) and reseed <em>pseudo-random-number-generator</em>.</p><p><em>source</em> may be <tt>:RANDOM</tt>, which indicates /dev/random or <tt>:URANDOM</tt>, which indicates /dev/urandom. On Windows, CryptGenRandom
+is always used.</p><div class="lisp-symbol"><a name="read-seed"></a><tt><strong>read-seed</strong> <em>pseudo-random-number-generator</em> <em>path</em> =&gt; <em>t</em></tt><br /></div><p>Read enough bytes from <em>path</em> to reseed <em>pseudo-random-number-generator</em>, then generate a pseudo-random seed
+and write it back to <em>path</em>. If <em>path</em> doesn't exist, calls <a href="#read-os-random-seed" style="symbol">read-os-random-seed</a> to get a truly random seed from the OS.</p><div class="lisp-symbol"><a name="write-seed"></a><tt><strong>write-seed</strong> <em>prng</em> <em>path</em> =&gt; <em>t</em></tt><br /></div><p>Generate enough random data to reseed <em>pseudo-random-number-generator</em>, then write it to <em>path</em>.</p><div class="lisp-symbol"><a name="random-bits"></a><tt><strong>random-bits</strong> <em>pseudo-random-number-generator</em> <em>num-bits</em> =&gt; <em>integer</em></tt><br /></div><p>Generate and integer with <em>num-bits</em> bits.</p><div class="lisp-symbol"><a name="strong-random"></a><tt><strong>strong-random</strong> <em>limit</em> <em><tt>&optional</tt></em> <em>prng</em> =&gt; <em>number</em></tt><br /></div><p>A drop-in replacement for <em>COMMON-LISP:RANDOM</em>, <tt>STRONG-RANDOM</tt> generates a number (and integer if <em>limit</em> is an
+integer and a float if it is a float) between 0 and <em>limit</em>-1 in an
+unbiased fashion.</p><h3>Fortuna</h3><p>Fortuna is a cryptographically-secure random number presented by
+Ferguson, Schneier and Kohno in <cite>Cryptography Engineering</cite>. It is built around 32 entropy pools, which are used with decreasing
+frequency for each reseed (e.g. pool 0 is used in each reseed, pool 1 in
+every other reseed, pool 2 in every fourth reseed and so forth). Pools
+are seeded with data from up to 256 sources.</p><div class="lisp-symbol"><a name="add-random-event"></a><tt><strong>add-random-event</strong> <em>pseudo-random-number-generator</em> <em>source</em> <em>pool-id</em> <em>event</em> =&gt; <em>pool-length</em></tt><br /></div><p>Add entropy to <em>pseudo-random-number-generator</em>.</p><p><em>source</em> is an integer in the range 0-255 specifiying the event's
+application-defined source.</p><p><em>pool-id</em> is an integer in the range 0-31 specifying the pool to top up.</p><p><em>event</em> is up to 32 bytes of data (for longer events, hash them
+down or break them up into chunks).</p><h2 id="gray-streams">Gray Streams</h2><p>Ironclad includes support for several convenient stream
abstractions based on Gray streams. Gray streams support in Ironclad is
included for SBCL, CMUCL, OpenMCL, Lispworks, and Allegro.</p><h3>Octet streams</h3><p>Octet streams are very similar to Common Lisp's <a href="#string-stream" style="symbol">string-stream</a>,
except they deal in octets instead of characters.</p><div class="lisp-symbol"><a name="make-octet-input-stream"></a><tt><strong>make-octet-input-stream</strong> <em>buffer</em> <em><tt>&optional</tt></em> <em>start</em> <em>end</em> =&gt; <em>octet-input-stream</em></tt><br /></div><p>As <a href="#make-string-input-stream" style="symbol">make-string-input-stream</a>, only with octets instead of characters.</p><div class="lisp-symbol"><a name="make-octet-output-stream"></a><tt><strong>make-octet-output-stream</strong> =&gt; <em>octet-output-stream</em></tt><br /></div><p>As <a href="#make-string-output-stream" style="symbol">make-string-output-stream</a>, only with octets instead of characters.</p><div class="lisp-symbol"><a name="get-output-stream-octets"></a><tt><strong>get-output-stream-octets</strong> <em>stream</em> =&gt; <em>octet-vector</em></tt><br /></div><p>As <a href="#get-output-stream-string" style="symbol">get-output-stream-string</a>, only with an octet output-steam
View
134 doc/ironclad-doc.txt
@@ -455,7 +455,7 @@ salt, as byte vectors.")
algorithm. Returns the derived hash of the password as a single string
that encodes the given salt and PBKDF2 algorithm parameters.")
-(:describe :function (ironclad:pbkdf2-check-password validp))
+(:describe :function (ironclad:pbkdf2-check-password boolean))
(:p "Given a " 'password' " byte vector and a combined salt and digest string
produced by " `pbkdf2-hash-password-to-combined-string` ", checks whether
@@ -574,6 +574,138 @@ values. " 'r' " and " 's' " may be either integers or they may be
(:describe :function (ironclad:decrypt-message decrypted-message))
+((:h2 id "prng") "Pseudo-random Number Generation")
+
+(:h3 "Example")
+
+(:p (:pre "> (setf *prng* (make-prng :fortuna))
+-> #<fortuna-prng>
+> (random-data 16)
+-> #(61 145 133 130 220 200 90 86 0 101 62 169 0 40 101 78)
+> (setf *prng* (make-prng :fortuna :seed nil))
+-> #<fortuna-prng>
+> (crypto:read-os-random-seed :random)
+-> 1
+> (crypto:strong-random 16)
+-> 3
+> (write-seed #P\"/tmp/seed\")
+-> t
+> (crypto:read-seed #P\"/tmp/seed\")
+-> t
+> (crypto:random-bits 16)
+-> 41546
+> (add-random-event 0 0 (string-to-octets \"foobar\"))"))
+
+(:p "See details about " @add-random-event " below.")
+
+(:describe :function (ironclad:make-prng prng))
+
+(:p "Create an unseeded pseudo-random number generator.")
+
+(:p 'name' " denotes the style of PRNG to use. " @list-all-prngs " will
+tell you the names of all supported PRNGs. Currently only Fortuna is
+supported. " 'name' " can be a symbol in the " `KEYWORD` " package or
+the " `IRONCLAD` " package.")
+
+(:p 'seed' " is a " (:em "seed descriptor") ". If NIL, the PRNG will
+not be seeded (which may prevent it from generating output until it is
+seeded, depending on the generator). If " `:RANDOM` " then the PRNG
+will be seeded with the OS's cryptographically-secure PRNG. If "
+`:URANDOM` " then the PRNG will be seeded with the OS's
+fast-but-potentially-less-secure PRNG, if available (if not, will
+fallback to " `:RANDOM` "). If it is a pathname indicator, a seed will
+be read from the indicated file, then a new seed will be generated and
+written back to the file (over-writing the old seed). Finally, if it is
+a byte vector, it will be used to seed the PRNG.")
+
+(:describe :function (ironclad:list-all-prngs list))
+
+(:p "List all known PRNG types.")
+
+(:describe :function (ironclad:random-data bytes))
+
+(:p "Generate " 'num-bytes' " bytes of random data from "
+'pseudo-random-number-generator' ". Updates the state of the generator.")
+
+(:describe :function (ironclad:read-os-random-seed reseed-count))
+
+(:p "Reads an OS-provided random seed (from /dev/urandom or /dev/random
+on Unix; CryptGenRandom on Windows) and reseed " 'pseudo-random-number-generator' ".")
+
+(:p 'source' " may be " `:RANDOM` ", which indicates /dev/random or "
+`:URANDOM` ", which indicates /dev/urandom. On Windows, CryptGenRandom
+is always used.")
+
+(:describe :function (ironclad:read-seed t))
+
+(:p "Read enough bytes from " 'path' " to reseed "
+'pseudo-random-number-generator' ", then generate a pseudo-random seed
+and write it back to " 'path' ". If " 'path' " doesn't exist, calls "
+@read-os-random-seed " to get a truly random seed from the OS. Note
+that reseeding does " (:em "not") " reset the generator's state to the
+seed value; rather, it " (:em "combines" ) " the generator's state with
+the seed to form a new state.")
+
+(:describe :function (ironclad:write-seed t))
+
+(:p "Generate enough random data to reseed "
+'pseudo-random-number-generator' ", then write it to " 'path' ".")
+
+(:describe :function (ironclad:random-bits integer))
+
+(:p "Generate and integer with " 'num-bits' " bits.")
+
+(:describe :function (ironclad:strong-random number))
+
+(:p "A drop-in replacement for " 'COMMON-LISP:RANDOM' ", "
+`STRONG-RANDOM` " generates a number (and integer if " 'limit' " is an
+integer and a float if it is a float) between 0 and " 'limit' "-1 in an
+unbiased fashion.")
+
+(:h3 "Fortuna")
+
+(:p "Fortuna is a cryptographically-secure random number presented by
+Ferguson, Schneier and Kohno in " (:cite "Cryptography Engineering")
+". It is built around 32 entropy pools, which are used with decreasing
+frequency for each reseed (e.g. pool 0 is used in each reseed, pool 1 in
+every other reseed, pool 2 in every fourth reseed and so forth). Pools
+are seeded with data from up to 256 sources.")
+
+(:p "Each application should have one or more entropy sources (say, one
+for each OS random number source, one for the low bits of the current
+time, one for the output of a particular command or group of commands
+and so forth). A source should be used to add randomness to each pool
+in order, so source 0 should top up pool 0, then pool 1, and so forth up
+to pool 31, then loop back to pool 1 again. Be very careful to spread
+entropy across all 32 pools.")
+
+(:p "Fortuna automatically feeds entropy from the pools back into its
+random state when " @random-data " is called, using a method designed to
+make it resistant to various avenues of attack; even in case of
+generator compromise it will return to a safe state within a bounded
+time.")
+
+(:p "For purposes of reseeding, Fortuna will not reseed until the first
+pool contains 128 bits of entropy; " '+min-pool-size+' " sets the number
+of bytes this is; it defaults to a very conservative 128, meaning that
+by default each byte of event is assumed to contain a single bit of
+randomness.")
+
+(:p "It also will not reseed more than ten times per second.")
+
+(:describe :function (ironclad:add-random-event pool-length))
+
+(:p "Add entropy to " 'pseudo-random-number-generator' ".")
+
+;; FIXME: how to create an ndash in this formatting language?
+(:p 'source' " is an integer in the range 0-255 specifiying the event's
+application-defined source.")
+
+(:p 'pool-id' " is an integer in the range 0-31 specifying the pool to top up.")
+
+(:p 'event' " is up to 32 bytes of data (for longer events, hash them
+down or break them up into chunks).")
+
((:h2 id "gray-streams") "Gray Streams")
(:p "Ironclad includes support for several convenient stream
View
14 ironclad.asd
@@ -32,7 +32,7 @@
:maintainer "Nathan Froyd <froydnj@gmail.com>"
:description "A cryptographic toolkit written in pure Common Lisp"
:default-component-class ironclad-source-file
- :depends-on (#+sbcl sb-rotate-byte nibbles)
+ :depends-on (#+sbcl sb-rotate-byte nibbles sb-posix)
:components ((:static-file "README")
(:static-file "LICENSE")
(:static-file "TODO")
@@ -118,7 +118,14 @@
:components
((:file "public-key")
(:file "dsa" :depends-on ("public-key"))
- (:file "rsa" :depends-on ("public-key"))))))
+ (:file "rsa" :depends-on ("public-key"))))
+ (:module "prng"
+ :depends-on ("digests" "ciphers")
+ :components
+ ((:file "prng")
+ (:file "fortuna" :depends-on ("prng"
+ "generator"))
+ (:file "generator")))))
(:module "doc"
:components
((:html-file "ironclad")
@@ -198,7 +205,7 @@
(asdf:defsystem ironclad-tests
:depends-on (ironclad)
- :version "0.5"
+ :version "0.6"
:in-order-to ((test-op (load-op :ironclad-tests)))
:components ((:module "testing"
:components
@@ -214,6 +221,7 @@
(:file "padding")
(:file "pkcs5")
(:file "ironclad")
+ (:file "prng")
;; test vectors
(:test-vector-file "crc24")
(:test-vector-file "crc32")
View
5 src/package.lisp
@@ -52,6 +52,11 @@
#:dsa-key-p #:dsa-key-q #:dsa-key-g #:dsa-key-y #:dsa-key-x
#:dsa-signature-r #:dsa-signature-s
+ ;; pseudo-random number generators
+ #:pseudo-random-number-generator #:list-all-prngs #:make-prng #:random-data
+ #:read-os-random-seed #:read-seed #:write-seed #:fortuna-prng
+ #:add-random-event #:fortuna #:strong-random #:random-bits #:*prng*
+
;; conditions
#:ironclad-error #:initialization-vector-not-supplied
#:invalid-initialization-vector #:invalid-key-length
View
125 src/prng/fortuna.lisp
@@ -0,0 +1,125 @@
+;;;; fortuna.lisp -- Fortuna PRNG
+
+(in-package :crypto)
+
+(defvar fortuna :fortuna)
+
+
+(defparameter +min-pool-size+
+ 128
+ "Minimum pool size before a reseed is allowed. This should be the
+ number of bytes of pool data that are likely to contain 128 bits of
+ entropy. Defaults to a pessimistic estimate of 1 bit of entropy per
+ byte.")
+
+(defclass pool ()
+ ((digest :initform (make-digest :sha256))
+ (length :initform 0))
+ (:documentation "A Fortuna entropy pool. DIGEST contains its current
+ state; LENGTH the length in bytes of the entropy it contains."))
+
+(defclass fortuna-prng (pseudo-random-number-generator)
+ ((pools :initform (loop for i from 1 to 32
+ collect (make-instance 'pool)))
+ (reseed-count :initform 0)
+ (last-reseed :initform 0)
+ (generator))
+ (:documentation "A Fortuna random number generator. Contains 32
+ entropy pools which are used to reseed GENERATOR."))
+
+(defmethod internal-random-data (num-bytes
+ (pseudo-random-number-generator
+ fortuna-prng))
+ (when (plusp num-bytes)
+ (with-slots (pools generator reseed-count last-reseed)
+ pseudo-random-number-generator
+ (when (and (>= (slot-value (first pools) 'length) +min-pool-size+)
+ (> (- (get-internal-run-time) last-reseed) 100))
+ (incf reseed-count)
+ (loop for i from 0 below (length pools)
+ with seed = (make-array (* (digest-length :sha256)
+ (integer-length
+ (logand reseed-count
+ (- reseed-count))))
+ :element-type '(unsigned-byte 8))
+ while (zerop (mod reseed-count (expt 2 i)))
+ collect (with-slots (digest length) (nth i pools)
+ (digest-sequence digest :digest seed :digest-start)
+ (digest-sequence :sha256 :digest seed :digest-start)
+ (setf length 0)
+ (reinitialize-instance digest))
+ finally (reseed generator seed)))
+ (assert (plusp reseed-count))
+ (pseudo-random-data generator num-bytes))))
+
+(defun add-random-event (source pool-id event
+ &optional (pseudo-random-number-generator *prng*))
+ (assert (and (<= 1 (length event) 32)
+ (<= 0 source 255)
+ (<= 0 pool-id 31)))
+ (let ((pool (nth pool-id (slot-value pseudo-random-number-generator 'pools))))
+ (update-digest (slot-value pool 'digest)
+ (concatenate '(vector (unsigned-byte 8))
+ (integer-to-octets source)
+ (integer-to-octets
+ (length event))
+ event))
+ (incf (slot-value pool 'length) (length event))))
+
+(defmethod internal-write-seed (path (pseudo-random-number-generator
+fortuna-prng))
+ (with-open-file (seed-file path
+ :direction :output
+ :if-exists :supersede
+ :if-does-not-exist :create
+ :element-type '(unsigned-byte 8))
+ (write-sequence (random-data 64 pseudo-random-number-generator) seed-file))
+ t)
+
+(defmethod internal-read-os-random-seed (source
+ (pseudo-random-number-generator
+ fortuna-prng))
+ "Read a random seed from /dev/random or equivalent."
+ (reseed (slot-value pseudo-random-number-generator 'generator)
+ (os-random-seed source 64))
+ (incf (slot-value pseudo-random-number-generator 'reseed-count)))
+
+(defmethod internal-read-seed (path
+ (pseudo-random-number-generator fortuna-prng))
+ (with-open-file (seed-file path
+ :direction :input
+ :element-type '(unsigned-byte 8))
+ (let ((seq (make-array 64 :element-type '(unsigned-byte 8))))
+ (assert (>= (read-sequence seq seed-file) 64))
+ (reseed (slot-value pseudo-random-number-generator 'generator) seq)
+ (incf (slot-value pseudo-random-number-generator 'reseed-count ))))
+ (write-seed path pseudo-random-number-generator))
+
+(defun feed-fifo (pseudo-random-number-generator path)
+ "Feed random data into a FIFO"
+ (loop while
+ (handler-case (with-open-file
+ (fortune-out path :direction :output
+ :if-exists :overwrite
+ :element-type '(unsigned-byte 8))
+ (loop do (write-sequence
+ (random-data (1- (expt 2 20))
+ pseudo-random-number-generator)
+ fortune-out)))
+ (stream-error () t))))
+
+(defun make-fortuna (cipher)
+ (let ((prng (make-instance 'fortuna-prng)))
+ (setf (slot-value prng 'generator)
+ (make-instance 'generator :cipher cipher))
+ prng))
+
+(defmethod make-prng ((name (eql :fortuna)) &key seed (cipher :aes))
+ (declare (ignorable seed))
+ (make-fortuna cipher))
+
+;; FIXME: this is more than a little ugly; maybe there should be a
+;; prng-registry or something?
+(defmethod make-prng ((name (eql 'fortuna)) &key seed (cipher :aes))
+ (declare (ignorable seed))
+ (make-fortuna cipher))
View
62 src/prng/generator.lisp
@@ -0,0 +1,62 @@
+;;;; generator.lisp -- Fortuna PRNG generator
+
+(in-package :crypto)
+
+
+
+(defvar +fortuna-cipher-block-size+ 16
+ "Fortuna is only defined for 128-bit (16-byte) cyphers")
+
+(defclass generator ()
+ ((key
+ :initform (make-array 32
+ :element-type '(unsigned-byte 8)
+ :initial-element 0))
+ (counter :initform 0)
+ (digest :initform (make-digest :sha256))
+ (cipher :initform nil))
+ (:documentation "Fortuna generator. KEY is the key used to initialise
+ CIPHER as an instance of CIPHER-NAME (which must be a valid NAME
+ recognised by MAKE-CIPHER)."))
+
+(defmethod initialize-instance :after ((generator generator) &key (cipher :aes))
+ (assert (= (block-length cipher) +fortuna-cipher-block-size+))
+ (assert (find 32 (key-lengths cipher)))
+ (with-slots (key (cipher-slot cipher)) generator
+ (setf cipher-slot
+ (make-cipher cipher :key key :mode :ecb))))
+
+(defun reseed (generator seed)
+ (with-slots (key counter cipher digest) generator
+ (reinitialize-instance digest)
+ (update-digest digest key)
+ (update-digest digest seed)
+ (produce-digest digest :digest key)
+ (reinitialize-instance digest)
+ (digest-sequence digest key :digest key)
+ (incf counter)
+ (reinitialize-instance cipher :key key)))
+
+(defun generate-blocks (generator num-blocks)
+ "Internal use only"
+ (with-slots (cipher key counter) generator
+ (assert (and cipher
+ (plusp counter)))
+ (loop for i from 1 to num-blocks
+ collect (let ((block (integer-to-octets counter
+ :n-bits 128
+ :big-endian nil)))
+ (encrypt-in-place cipher block)
+ block)
+ into blocks
+ do (incf counter)
+ finally (return (apply #'concatenate 'simple-octet-vector blocks)))))
+
+(defun pseudo-random-data (generator num-bytes)
+ (assert (< 0 num-bytes (expt 2 20)))
+ (let* ((output (subseq (generate-blocks generator (ceiling num-bytes 16))
+ 0
+ num-bytes))
+ (key (generate-blocks generator 2)))
+ (setf (slot-value generator 'key) key)
+ output))
View
114 src/prng/prng.lisp
@@ -0,0 +1,114 @@
+;;;; prng.lisp -- common functions for pseudo-random number generators
+
+(in-package :crypto)
+
+
+(defvar *prng* nil
+ "Default pseudo-random-number generator for use by all crypto
+ functions; the user must initialize it, e.g. with (setf
+ crypto:*prng* (crypto:make-prng :fortuna)).")
+
+(defclass pseudo-random-number-generator ()
+ ()
+ (:documentation "A pseudo random number generator. Base class for
+ other PRNGs; not intended to be instantiated."))
+
+(defun list-all-prngs ()
+ '(fortuna))
+
+(defgeneric make-prng (name &key seed)
+ (:documentation "Create a new NAME-type random number generator,
+ seeding it from SEED. If SEED is a pathname or namestring, read data
+ from the indicated file; if it is sequence of bytes, use those bytes
+ directly; if it is :RANDOM then read from /dev/random; if it
+ is :URANDOM then read from /dev/urandom; if it is NIL then the
+ generator is not seeded."))
+
+(defmethod make-prng :around (name &key (seed :random))
+ (let ((prng (call-next-method)))
+ (cond
+ ((eq seed nil))
+ ((find seed '(:random :urandom)) (read-os-random-seed seed prng))
+ ((or (pathnamep seed) (stringp seed)) (read-seed seed prng))
+ ((typep seed 'simple-octet-vector)
+ (reseed (slot-value prng 'generator) seed)
+ (incf (slot-value prng 'reseed-count)))
+ (t (error "SEED must be an octet vector, pathname indicator, :random or :urandom")))
+ prng))
+
+(defun random-data (num-bytes &optional (pseudo-random-number-generator *prng*))
+ (internal-random-data num-bytes pseudo-random-number-generator))
+
+(defgeneric internal-random-data (num-bytes pseudo-random-number-generator)
+ (:documentation "Generate NUM-BYTES bytes using
+ PSEUDO-RANDOM-NUMBER-GENERATOR"))
+
+(defun random-bits (num-bits &optional (pseudo-random-number-generator *prng*))
+ (logand (1- (expt 2 num-bits))
+ (octets-to-integer
+ (internal-random-data (ceiling num-bits 8) pseudo-random-number-generator))))
+
+(defun strong-random (limit &optional (prng *prng*))
+ "Return a strong random number from 0 to limit-1 inclusive. A drop-in
+replacement for COMMON-LISP:RANDOM."
+ (assert (plusp limit))
+ (assert prng)
+ (etypecase limit
+ (integer
+ (let* ((log-limit (log limit 2))
+ (num-bytes (ceiling log-limit 8))
+ (mask (1- (expt 2 (ceiling log-limit)))))
+ (loop for random = (logand (ironclad:octets-to-integer
+ (internal-random-data num-bytes prng))
+ mask)
+ until (< random limit)
+ finally (return random))))
+ (float
+ (float (let ((floor (floor 1 long-float-epsilon)))
+ (* limit
+ (/ (strong-random floor)
+ floor)))))))
+
+(defun os-random-seed (source num-bytes)
+ #+unix(let ((path (cond
+ ((eq source :random) #P"/dev/random")
+ ((eq source :urandom) #P"/dev/urandom")
+ (t (error "Source must be either :random or :urandom"))))
+ (seq (make-array num-bytes :element-type '(unsigned-byte 8))))
+ (with-open-file (seed-file path :element-type '(unsigned-byte 8))
+ (assert (>= (read-sequence seq seed-file) num-bytes))
+ seq))
+ ;; FIXME: this is _untested_!
+ #+(and win32 sb-dynamic-core)(sb!win32:crypt-gen-random num-bytes)
+ #-(or unix (and win32 sb-dynamic-core))(error "OS-RANDOM-SEED is not supported on your platform."))
+
+(defun read-os-random-seed (source &optional (prng *prng*))
+ (internal-read-os-random-seed source prng)
+ t)
+
+(defgeneric internal-read-os-random-seed (source prng)
+ (:documentation "(Re)seed PRNG from SOURCE. SOURCE may be :random
+ or :urandom"))
+
+(defun read-seed (path &optional (pseudo-random-number-generator *prng*))
+ "Reseed PSEUDO-RANDOM-NUMBER-GENERATOR from PATH. If PATH doesn't
+exist, reseed from /dev/random and then write that seed to PATH."
+ (if (probe-file path)
+ (internal-read-seed path pseudo-random-number-generator)
+ (progn
+ (read-os-random-seed pseudo-random-number-generator)
+ (write-seed path pseudo-random-number-generator)
+ ;; FIXME: this only works under SBCL. It's important, though,
+ ;; as it sets the proper permissions for reading a seedfile.
+ #+sbcl(sb-posix:chmod path (logior sb-posix:S-IRUSR sb-posix:S-IWUSR))))
+ t)
+
+(defgeneric internal-read-seed (path prng)
+ (:documentation "Reseed PRNG from PATH."))
+
+(defun write-seed (path &optional (prng *prng*))
+ (internal-write-seed path prng))
+
+(defgeneric internal-write-seed (path prng)
+ (:documentation "Write enough random data from PRNG to PATH to
+ properly reseed it."))
View
5 testing/test-vectors/prng.lisp
@@ -0,0 +1,5 @@
+(in-package :crypto-tests)
+
+(rtest:deftest :prng-fortuna (run-test-vector-file :prng *prng-tests*) t)
+
+;; (random-data (make-prng :fortuna :seed (coerce #(0) 'simple-octet-vector)) 1) #(28))
View
17 testing/test-vectors/prng.testvec
@@ -0,0 +1,17 @@
+;; each test should be written by reasoning from the definition in Cryptography
+;; Engineering, _not_ by checking what the implementation currently does
+
+;; FIXME: this test was written from current output, not reasoning
+(:generator-test :aes
+ (#(0 1 2 3))
+ (#(44 116 1 176 208 92 127 135 160 115 184 21 0 211 133 80)
+ #(247 244 30 42 191 37 124 105 221 173 67 94 54 109 139 209)
+ #(252 200 2 53 195 193 73 89 24 147 7 11 68 110 45 48)
+ #(75 89 212 151 2 169 142 105 253 209 190 32 211 209 77 107)))
+
+;; FIXME: this test was written from current output, not reasoning
+;;(:fortuna-test #(0 1 2 3)
+;; ((0 0 #(0 0 0 0)))
+;; #(137 205 83 241 66 231 102 41 140 77 103
+;; 232 6 233 4 112 137 168 106 238 93 35 73
+;; 66 123 59 154 60 252 2 145 225 163 142 31 183))
View
29 testing/testfuns.lisp
@@ -220,3 +220,32 @@
(defparameter *mac-tests*
(list (cons :hmac-test 'hmac-test)
(cons :cmac-test 'cmac-test)))
+
+
+;;; PRNG testing routines
+(defun fortuna-test (name seed entropy expected-sequence)
+ (let ((prng (crypto:make-prng :fortuna
+ :seed (coerce seed 'crypto::simple-octet-vector)))
+ (num-bytes (length expected-sequence)))
+ (loop for (source pool-id event) in entropy
+ do (crypto:add-random-event prng source pool-id event))
+ (equalp expected-sequence
+ (crypto:random-data prng num-bytes))))
+
+
+(defun generator-test (name cipher seeds expected-sequences)
+ (declare (ignore name))
+ (let ((generator (make-instance 'crypto::generator :cipher cipher)))
+ (loop for seed in seeds
+ do (crypto::reseed generator (coerce seed '(vector (unsigned-byte 8)))))
+ (every (lambda (sequence)
+ (assert (zerop (mod (length sequence) 16)))
+ (equalp sequence
+ (crypto::generate-blocks generator
+ (/ (length sequence) 16))))
+ expected-sequences)))
+
+
+(defparameter *prng-tests*
+ `((:fortuna-test . ,#'fortuna-test)
+ (:generator-test . ,#'generator-test)))

0 comments on commit 6428165

Please sign in to comment.