diff --git a/lib/db/json.js b/lib/db/json.js
index 4bb594f48fdf7c9ae9603dd4b6684e9398dbd81a..18ef914b1c3358d640060eb6da2f17b3f9fc5983 100644
--- a/lib/db/json.js
+++ b/lib/db/json.js
@@ -175,35 +175,35 @@ function addEmailToAccount(existing_email, email, cb) {
 }
 
 exports.stageUser = function(email, cb) {
-  var secret = secrets.generate(48);
-
-  // overwrite previously staged users
-  sync();
-  db.staged[secret] = {
-    type: "add_account",
-    email: email,
-    when: (new Date()).getTime()
-  };
-  db.stagedEmails[email] = secret;
-  flush();
-  setTimeout(function() { cb(secret); }, 0);
+  secrets.generate(48, function(secret) {
+    // overwrite previously staged users
+    sync();
+    db.staged[secret] = {
+      type: "add_account",
+      email: email,
+      when: (new Date()).getTime()
+    };
+    db.stagedEmails[email] = secret;
+    flush();
+    setTimeout(function() { cb(secret); }, 0);
+  });
 };
 
 exports.stageEmail = function(existing_email, new_email, cb) {
-  var secret = secrets.generate(48);
-
-  // overwrite previously staged users
-  sync();
-  db.staged[secret] = {
-    type: "add_email",
-    existing_email: existing_email,
-    email: new_email,
-    when: (new Date()).getTime()
-  };
-  db.stagedEmails[new_email] = secret;
-  flush();
-
-  setTimeout(function() { cb(secret); }, 0);
+  secrets.generate(48, function(secret) {
+    // overwrite previously staged users
+    sync();
+    db.staged[secret] = {
+      type: "add_email",
+      existing_email: existing_email,
+      email: new_email,
+      when: (new Date()).getTime()
+    };
+    db.stagedEmails[new_email] = secret;
+    flush();
+    
+    setTimeout(function() { cb(secret); }, 0);
+  });
 };
 
 
diff --git a/lib/db/mysql.js b/lib/db/mysql.js
index 20e526a0212589952ea6ff4c01f8a11a9e791126..2c1a608f365138c2c441d41b45be8568c94b1e99 100644
--- a/lib/db/mysql.js
+++ b/lib/db/mysql.js
@@ -252,21 +252,22 @@ exports.lastStaged = function(email, cb) {
       else cb(new Date(rows[0].ts * 1000));
     }
   );
-}
+};
 
 exports.stageUser = function(email, cb) {
-  var secret = secrets.generate(48);
-  // overwrite previously staged users
-  client.query('INSERT INTO staged (secret, new_acct, email) VALUES(?,TRUE,?) ' +
-               'ON DUPLICATE KEY UPDATE secret=?, existing="", new_acct=TRUE, ts=NOW()',
-               [ secret, email, secret],
-               function(err) {
-                 if (err) {
-                   logUnexpectedError(err);
-                   cb(undefined, err);
-                 } else cb(secret);
-               });
-}
+  secrets.generate(48, function(secret) {
+    // overwrite previously staged users
+    client.query('INSERT INTO staged (secret, new_acct, email) VALUES(?,TRUE,?) ' +
+                 'ON DUPLICATE KEY UPDATE secret=?, existing="", new_acct=TRUE, ts=NOW()',
+                 [ secret, email, secret],
+                 function(err) {
+                   if (err) {
+                     logUnexpectedError(err);
+                     cb(undefined, err);
+                   } else cb(secret);
+                 });
+  });
+};
 
 exports.emailForVerificationSecret = function(secret, cb) {
   client.query(
@@ -362,19 +363,20 @@ exports.emailsBelongToSameAccount = function(lhs, rhs, cb) {
 }
 
 exports.stageEmail = function(existing_email, new_email, cb) {
-  var secret = secrets.generate(48);
-  // overwrite previously staged users
-  client.query('INSERT INTO staged (secret, new_acct, existing, email) VALUES(?,FALSE,?,?) ' +
-               'ON DUPLICATE KEY UPDATE secret=?, existing=?, new_acct=FALSE, ts=NOW()',
-               [ secret, existing_email, new_email, secret, existing_email],
-               function(err) {
-                 if (err) {
-                   logUnexpectedError(err);
-                   cb(undefined, err);
-                 }
-                 else cb(secret);
-               });
-}
+  secrets.generate(48, function(secret) {
+    // overwrite previously staged users
+    client.query('INSERT INTO staged (secret, new_acct, existing, email) VALUES(?,FALSE,?,?) ' +
+                 'ON DUPLICATE KEY UPDATE secret=?, existing=?, new_acct=FALSE, ts=NOW()',
+                 [ secret, existing_email, new_email, secret, existing_email],
+                 function(err) {
+                   if (err) {
+                     logUnexpectedError(err);
+                     cb(undefined, err);
+                   }
+                   else cb(secret);
+                 });
+  });
+};
 
 exports.checkAuth = function(email, cb) {
   client.query(
diff --git a/lib/secrets.js b/lib/secrets.js
index 8ba989a25eee5c7536a765d1c5fe1e7c89533b9a..b252e803b35a163ec9fc5a2f99dfe210c3bda300 100644
--- a/lib/secrets.js
+++ b/lib/secrets.js
@@ -38,32 +38,44 @@ path = require('path'),
 fs = require('fs'),
 jwk = require('jwcrypto/jwk'),
 jwt = require('jwcrypto/jwt'),
-Buffer = require('buffer').Buffer;
+Buffer = require('buffer').Buffer,
+crypto = require('crypto');
 
-var devRandom = fs.openSync('/dev/urandom', 'r');
+// make this async capable
+function bytesToChars(buf) {
+  var str = "";
+  const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 
-function randomBytes(length) {
-  var buf = new Buffer(length);
-  fs.readSync(devRandom, buf, 0, buf.length, 0);
-  return buf;
+  // yes, we are biasing the output here a bit.
+  // I'm ok with that. We can improve this over time.
+  for (var i=0; i < buf.length; i++) {
+    str += alphabet.charAt(buf[i] % alphabet.length);
+  }
+  
+  return str;
 }
 
-exports.randomBytes = randomBytes;
+exports.generate = function(chars, cb) {
+  if (cb) {
+    crypto.randomBytes(chars, function(ex, buf) {
+      cb(bytesToChars(buf));
+    });
+  } else {
+    return bytesToChars(crypto.randomBytes(chars));
+  }
+};
 
-exports.generate = function(chars) {
+// we don't bother to make this async, cause it's not needed
+exports.weakGenerate = function(chars) {
   var str = "";
   const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 
-  var bytes = randomBytes(chars);
-
-  // yes, we are biasing the output here a bit.
-  // I'm ok with that. We can improve this over time.
   for (var i=0; i < chars; i++) {
-    str += alphabet.charAt(bytes[i] % alphabet.length);
+    str += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
   }
 
   return str;
-}
+};
 
 // functions to set defaults
 
diff --git a/tests/secrets-test.js b/tests/secrets-test.js
new file mode 100644
index 0000000000000000000000000000000000000000..1d5ef05caa73814195138b36420fa05119935d3b
--- /dev/null
+++ b/tests/secrets-test.js
@@ -0,0 +1,89 @@
+#!/usr/bin/env node
+
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla BrowserID.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+require('./lib/test_env.js');
+
+const assert = require('assert'),
+vows = require('vows'),
+secrets = require('../lib/secrets');
+
+var suite = vows.describe('secrets');
+
+var LENGTH = 10;
+
+function make_secrets_batch(rand_func) {
+  return {
+    "generate a secret": {
+      topic: function() {
+        return rand_func(LENGTH);
+      },
+      "of proper length" : function(err, s) {
+        assert.equal(s.length, LENGTH);
+      }
+    },
+    "two secrets": {
+      topic: function() {
+        return {
+          s1: rand_func(LENGTH),
+          s2: rand_func(LENGTH)
+        };
+      },
+      "are not equal" : function(err, the_secrets) {
+        assert.notEqual(the_secrets.s1, the_secrets.s2);
+      }
+    }  
+  };
+};
+
+// check that we can generate random secrets
+suite.addBatch(make_secrets_batch(secrets.generate));
+suite.addBatch(make_secrets_batch(secrets.weakGenerate));
+
+// and the async one
+suite.addBatch({
+  "generate a secret": {
+    topic: function() {
+      secrets.generate(LENGTH, this.callback);
+    },
+    "of proper length" : function(s, err) {
+      assert.equal(s.length, LENGTH);
+    }
+  }  
+});
+// run or export the suite.
+if (process.argv[1] === __filename) suite.run();
+else suite.export(module);