diff --git a/source/calendarwebapp/authenticator.d b/source/calendarwebapp/authenticator.d
index 8f44335..5f79986 100644
--- a/source/calendarwebapp/authenticator.d
+++ b/source/calendarwebapp/authenticator.d
@@ -1,5 +1,7 @@
 module calendarwebapp.authenticator;
 
+import calendarwebapp.passhash : PasswordHasher;
+
 import poodinis;
 
 import std.range : InputRange;
@@ -21,6 +23,7 @@ class MongoDBAuthenticator(Collection = MongoCollection) : Authenticator
 private:
     @Value("users")
     Collection users;
+    @Autowire PasswordHasher passwordHasher;
 
 public:
     Nullable!AuthInfo checkUser(string username, string password) @safe
@@ -28,13 +31,13 @@ public:
         import botan.passhash.bcrypt : checkBcrypt;
 
         auto result = users.findOne(["username" : username]);
-        /* checkBcrypt should be called using vibe.core.concurrency.async to
+        /* checkHash should be called using vibe.core.concurrency.async to
            avoid blocking, but https://github.com/vibe-d/vibe.d/issues/1521 is
            blocking this */
         if (result != Bson(null))
         {
             auto authInfo = result.deserializeBson!AuthInfo;
-            if ((()@trusted => checkBcrypt(password, authInfo.passwordHash))())
+            if (passwordHasher.checkHash(password, authInfo.passwordHash))
             {
                 return authInfo.nullable;
             }
diff --git a/source/calendarwebapp/calendarwebapp.d b/source/calendarwebapp/calendarwebapp.d
index 38efaae..5757bfb 100644
--- a/source/calendarwebapp/calendarwebapp.d
+++ b/source/calendarwebapp/calendarwebapp.d
@@ -1,9 +1,8 @@
 module calendarwebapp.calendarwebapp;
 
-import botan.rng.rng : RandomNumberGenerator;
-
 import calendarwebapp.authenticator;
 import calendarwebapp.event;
+import calendarwebapp.passhash : PasswordHasher;
 
 import core.time : days;
 
@@ -108,12 +107,10 @@ public:
     }
 
     @auth(Role.admin) @errorDisplay!getCreateuser void postCreateuser(string username,
-            string password, Privilege role)
+            string password, Privilege role) @safe
     {
-        import botan.passhash.bcrypt;
-
         authenticator.addUser(AuthInfo(BsonObjectID.generate, username,
-                generateBcrypt(password, rng, 10), role));
+                passwordHasher.generateHash(password), role));
         redirect("/users");
     }
 
@@ -129,5 +126,5 @@ private:
 
     @Autowire EventStore eventStore;
     @Autowire Authenticator authenticator;
-    @Autowire RandomNumberGenerator rng;
+    @Autowire PasswordHasher passwordHasher;
 }
diff --git a/source/calendarwebapp/configuration.d b/source/calendarwebapp/configuration.d
index 6a33e86..21a5465 100644
--- a/source/calendarwebapp/configuration.d
+++ b/source/calendarwebapp/configuration.d
@@ -6,6 +6,7 @@ import botan.rng.rng : RandomNumberGenerator;
 import calendarwebapp.authenticator : Authenticator, MongoDBAuthenticator;
 import calendarwebapp.calendarwebapp : CalendarWebapp;
 import calendarwebapp.event : EventStore, MongoDBEventStore;
+import calendarwebapp.passhash : BcryptPasswordHasher, PasswordHasher;
 
 import poodinis;
 
@@ -22,6 +23,7 @@ public:
         container.register!MongoClient.existingInstance(mongoClient);
         container.register!(EventStore, MongoDBEventStore!());
         container.register!(Authenticator, MongoDBAuthenticator!());
+        container.register!(PasswordHasher, BcryptPasswordHasher);
         container.register!(RandomNumberGenerator, AutoSeededRNG);
         container.register!CalendarWebapp;
         container.register!(ValueInjector!string, StringInjector);
diff --git a/source/calendarwebapp/passhash.d b/source/calendarwebapp/passhash.d
new file mode 100644
index 0000000..1cdb57a
--- /dev/null
+++ b/source/calendarwebapp/passhash.d
@@ -0,0 +1,29 @@
+module calendarwebapp.passhash;
+
+import poodinis;
+
+interface PasswordHasher
+{
+    string generateHash(in string password) @safe;
+    bool checkHash(in string password, in string hash) @safe;
+}
+
+class BcryptPasswordHasher : PasswordHasher
+{
+    import botan.passhash.bcrypt : checkBcrypt, generateBcrypt;
+    import botan.rng.rng : RandomNumberGenerator;
+
+    string generateHash(in string password) @safe
+    {
+        return (() @trusted => generateBcrypt(password, rng, cost))();
+    }
+
+    bool checkHash(in string password, in string hash) @safe
+    {
+        return (()@trusted => checkBcrypt(password, hash))();
+    }
+
+private:
+    @Autowire RandomNumberGenerator rng;
+    enum cost = 10;
+}