[Libguestfs] [libguestfs] make_random_password(): avoid modulo bias, and do not deplete system entropy (#9)

Richard W.M. Jones rjones at redhat.com
Thu Nov 14 09:51:27 UTC 2013


On Thu, Nov 14, 2013 at 01:48:17AM -0800, edwintorok wrote:
> Following the link to builder.ml from your blogpost I noticed the make_random_password () function, and I have some suggestions, well nitpicks really. See the 2 commits from this pull request.
> 
> 1. Using Ocaml's buffered I/O means that one make_random_password() call reads 64k bytes from /dev/urandom which drops the system's entropy to minimum ~128 bits. The fix is to use unbuffered I/O from the Unix module.
> 
> 2. There is a modulo bias when generating the random password: [0,256) mod 60 won't give you uniformly distributed bytes. In this particular case it probably doesn't matter, but you never know when someone copy+pastes your code into their project thinking this is a proper way to generate random passwords, so IMHO its best to avoid the modulo bias.
> See here for more details: http://eternallyconfuzzled.com/arts/jsw_art_rand.aspx
> And see arc4random_uniform's implementation: http://www.openbsd.org/cgi-bin/cvsweb/src/lib/libc/crypt/arc4random.c?rev=1.26;content-type=text%2Fplain
> 
> 3. The generated password needs ~2^107 brute-force attempts (16 * log2(60) + log2(default_rounds=5000)), which is more than enough of course, but usually 128-bit strength is used for keys. A password length of 20 characters would achieve that. My pull request doesn't include this change, its up to you.
> You can merge this Pull Request by running:
> 
>   git pull https://github.com/edwintorok/libguestfs make_random_password-fixes
> 
> Or you can view, comment on it, or merge it online at:
> 
>   https://github.com/libguestfs/libguestfs/pull/9
> 
> -- Commit Summary --
> 
>   * Do not use buffered reads on /dev/urandom
>   * Avoid modulo bias in random password generation
> 
> -- File Changes --
> 
>     M builder/builder.ml (22)
> 
> -- Patch Links --
> 
> https://github.com/libguestfs/libguestfs/pull/9.patch
> https://github.com/libguestfs/libguestfs/pull/9.diff

I'm posting this patch to the list for review.  Please follow
up there as we don't use github pull requests.

Rich.

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
libguestfs lets you edit virtual machines.  Supports shell scripting,
bindings from many languages.  http://libguestfs.org
-------------- next part --------------
>From cc4fd66701c7e0a5c72614d695565ba15f2e9212 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?T=C3=B6r=C3=B6k=20Edwin?= <edwin at etorok.net>
Date: Thu, 14 Nov 2013 11:13:50 +0200
Subject: [PATCH 1/2] Do not use buffered reads on /dev/urandom

in_channel has a 64k buffer, so one make_random_password () call removes a lot of the system's entropy
(for example /proc/sys/kernel/random/entropy_avail goes from ~3000 to 128)
---
 builder/builder.ml | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/builder/builder.ml b/builder/builder.ml
index 874dc8d..55e639e 100644
--- a/builder/builder.ml
+++ b/builder/builder.ml
@@ -448,6 +448,14 @@ let main () =
    * Note 'None' means that we randomize the root password.
    *)
   let () =
+    let read_byte fd =
+      let s = String.make 1 ' ' in
+      fun () ->
+        if read fd s 0 1 = 0 then
+          raise End_of_file;
+        Char.code s.[0]
+    in
+
     let make_random_password () =
       (* Get random characters from the set [A-Za-z0-9] with some
        * homoglyphs removed.
@@ -456,12 +464,12 @@ let main () =
         "ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789" in
       let nr_chars = String.length chars in
 
-      let chan = open_in "/dev/urandom" in
+      let fd = openfile "/dev/urandom" [O_RDONLY] 0 in
       let buf = String.create 16 in
       for i = 0 to 15 do
-        buf.[i] <- chars.[Char.code (input_char chan) mod nr_chars]
+        buf.[i] <- chars.[read_byte fd () mod nr_chars]
       done;
-      close_in chan;
+      close fd;
 
       buf
     in
-- 
1.8.4


>From 4cd1763d1313c102234f8b5fb40f7124795035ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?T=C3=B6r=C3=B6k=20Edwin?= <edwin at etorok.net>
Date: Thu, 14 Nov 2013 11:20:07 +0200
Subject: [PATCH 2/2] Avoid modulo bias in random password generation

Char.code (input_char chan) mod nr_chars has modulo bias because
the original interval is not a multiple of the destination interval,
i.e. 256 mod nr_chars != 0.

One way to fix this is to keep generating random numbers until they fall outside
the interval where modulo bias occurs, that is accept only c=[256 % nr_chars, 256).
That interval maps back to [0, nr_chars), and has a length of
(256 - 256 % nr_chars), which is a multiple of nr_chars.
---
 builder/builder.ml | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/builder/builder.ml b/builder/builder.ml
index 55e639e..fd335df 100644
--- a/builder/builder.ml
+++ b/builder/builder.ml
@@ -456,6 +456,14 @@ let main () =
         Char.code s.[0]
     in
 
+    (* return a random number uniformly distributed in [0, upper_bound)
+     * avoiding modulo bias *)
+    let rec uniform_random read upper_bound =
+      let c = read () in
+      if c >= 256 mod upper_bound then c mod upper_bound
+      else uniform_random read upper_bound
+    in
+
     let make_random_password () =
       (* Get random characters from the set [A-Za-z0-9] with some
        * homoglyphs removed.
@@ -467,7 +475,7 @@ let main () =
       let fd = openfile "/dev/urandom" [O_RDONLY] 0 in
       let buf = String.create 16 in
       for i = 0 to 15 do
-        buf.[i] <- chars.[read_byte fd () mod nr_chars]
+        buf.[i] <- chars.[uniform_random (read_byte fd) nr_chars]
       done;
       close fd;
 
-- 
1.8.4



More information about the Libguestfs mailing list