From 79e8defa97b974b1c3f63701c0a3411a958c5499 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=B6r=C3=B6k=20Edwin?= Date: Thu, 14 Nov 2013 11:13:50 +0200 Subject: [PATCH] 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. RWMJ: - Modify the code so it goes into a utility library. - Use the same code across virt-builder and virt-sysprep. (cherry picked from commit 6a1061663fa467d43777a8ed98f4f07750125012) --- builder/builder.ml | 11 +---------- mllib/password.ml | 10 +--------- mllib/urandom.ml | 21 +++++++++++++++++++++ mllib/urandom.mli | 4 ++++ 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/builder/builder.ml b/builder/builder.ml index 84aa869..15f920c 100644 --- a/builder/builder.ml +++ b/builder/builder.ml @@ -454,16 +454,7 @@ let main () = *) let chars = "ABCDEFGHIJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789" in - let nr_chars = String.length chars in - - let chan = open_in "/dev/urandom" in - let buf = String.create 16 in - for i = 0 to 15 do - buf.[i] <- chars.[Char.code (input_char chan) mod nr_chars] - done; - close_in chan; - - buf + Urandom.urandom_uniform 16 chars in let root_password = diff --git a/mllib/password.ml b/mllib/password.ml index 50c0683..a7a97b6 100644 --- a/mllib/password.ml +++ b/mllib/password.ml @@ -57,7 +57,6 @@ and read_password_from_file filename = (* Permissible characters in a salt. *) let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./" -let nr_chars = String.length chars let rec set_linux_passwords ~prog ?password_crypto g root passwords = let crypto = @@ -95,14 +94,7 @@ let rec set_linux_passwords ~prog ?password_crypto g root passwords = *) and encrypt password crypto = (* Get random characters from the set [A-Za-z0-9./] *) - let salt = - let chan = open_in "/dev/urandom" in - let buf = String.create 16 in - for i = 0 to 15 do - buf.[i] <- chars.[Char.code (input_char chan) mod nr_chars] - done; - close_in chan; - buf in + let salt = Urandom.urandom_uniform 16 chars in let salt = (match crypto with | `MD5 -> "$1$" diff --git a/mllib/urandom.ml b/mllib/urandom.ml index 3df9b7a..9b613e8 100644 --- a/mllib/urandom.ml +++ b/mllib/urandom.ml @@ -46,3 +46,24 @@ let urandom_bytes n = done; close fd; ret + +(* 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 + +let urandom_uniform n chars = + assert (n > 0); + let nr_chars = String.length chars in + assert (nr_chars > 0); + + let ret = String.make n ' ' in + let fd = open_urandom_fd () in + for i = 0 to n-1 do + ret.[i] <- chars.[uniform_random (read_byte fd) nr_chars] + done; + close fd; + ret diff --git a/mllib/urandom.mli b/mllib/urandom.mli index a7d7808..ffc77dd 100644 --- a/mllib/urandom.mli +++ b/mllib/urandom.mli @@ -20,3 +20,7 @@ val urandom_bytes : int -> string (** Read N bytes from /dev/urandom and return it as a binary string. *) + +val urandom_uniform : int -> string -> string +(** [urandom_uniform n chars] returns [n] bytes, uniformly + distributed from the sets of characters [chars]. *) -- 1.8.3.1