diff --git a/src/_zkapauthorizer/_plugin.py b/src/_zkapauthorizer/_plugin.py
index 1ce34fed2774c545296ecf5ad330a32c8c3e2281..a72586d8e69bb66c8eec1913c2a7c0e71aea90f7 100644
--- a/src/_zkapauthorizer/_plugin.py
+++ b/src/_zkapauthorizer/_plugin.py
@@ -142,10 +142,10 @@ class ZKAPAuthorizer(object):
         kwargs = configuration.copy()
         root_url = kwargs.pop(u"ristretto-issuer-root-url")
         pass_value = int(kwargs.pop(u"pass-value", BYTES_PER_PASS))
-        signing_key = SigningKey.decode_base64(
+        signing_key = load_signing_key(
             FilePath(
                 kwargs.pop(u"ristretto-signing-key-path"),
-            ).getContent(),
+            ),
         )
         announcement = {
             u"ristretto-issuer-root-url": root_url,
@@ -288,3 +288,23 @@ def get_root_nodes(client_node, node_config):
         return []
     else:
         return [client_node.create_node_from_uri(rootcap)]
+
+
+def load_signing_key(path):
+    """
+    Read a serialized Ristretto signing key from the given path and return it
+    as a ``challenge_bypass_ristretto.SigningKey``.
+
+    Unlike ``challenge_bypass_ristretto.SigningKey.decode_base64`` this
+    function will clean up any whitespace around the key.
+
+    :param FilePath path: The path from which to read the key.
+
+    :raise challenge_bypass_ristretto.DecodeException: If
+        ``SigningKey.decode_base64`` raises this exception it will be passed
+        through.
+
+    :return challenge_bypass_ristretto.SigningKey: An object representing the
+        key read.
+    """
+    return SigningKey.decode_base64(path.getContent().strip())
diff --git a/src/_zkapauthorizer/tests/strategies.py b/src/_zkapauthorizer/tests/strategies.py
index fde7590c73ab26ee8ac784fc3955baa1bdb736df..77c854a2bff7109130f8b41257fbca9671ce3262 100644
--- a/src/_zkapauthorizer/tests/strategies.py
+++ b/src/_zkapauthorizer/tests/strategies.py
@@ -35,6 +35,7 @@ from zope.interface import (
 
 from hypothesis.strategies import (
     one_of,
+    sampled_from,
     just,
     none,
     binary,
@@ -915,3 +916,33 @@ def api_auth_tokens():
     authorization tokens.
     """
     return binary(min_size=32, max_size=32).map(b64encode)
+
+
+def ristretto_signing_keys():
+    """
+    Build byte strings holding base64-encoded Ristretto signing keys, perhaps
+    with leading or trailing whitespace.
+    """
+    keys = sampled_from([
+        # A few legit keys
+        b"mkQf85V2vyLQRUYuqRb+Ke6K+M9pOtXm4MslsuCdBgg=",
+        b"6f93OIdZHHAmSIaRXDSIU1UcN+sbDAh41TRPb5DhrgI=",
+        b"k58h8yPT18epw+EKMJhwHFfoM6r3TIExKm4efQHNBgM=",
+        b"rbaAlWZ3NCnl5oZ9meviGfpLbyJpgpuiuFOX0rLnNwQ=",
+    ])
+    whitespace = sampled_from([
+        # maybe no whitespace at all
+        b""
+        # or maybe some
+        b" ",
+        b"\t",
+        b"\n",
+        b"\r\n",
+    ])
+
+    return builds(
+        lambda leading, key, trailing: leading + key + trailing,
+        whitespace,
+        keys,
+        whitespace,
+    )
diff --git a/src/_zkapauthorizer/tests/test_plugin.py b/src/_zkapauthorizer/tests/test_plugin.py
index 0349dde406b8b78cc06b452b57ac8647d6a7b2dd..1d460324c840460b7c5162bdea0ae677adb96d39 100644
--- a/src/_zkapauthorizer/tests/test_plugin.py
+++ b/src/_zkapauthorizer/tests/test_plugin.py
@@ -110,6 +110,10 @@ from twisted.plugins.zkapauthorizer import (
     storage_server,
 )
 
+from challenge_bypass_ristretto import (
+    SigningKey,
+)
+
 from ..spending import (
     GET_PASSES,
 )
@@ -134,6 +138,10 @@ from ..lease_maintenance import (
     SERVICE_NAME,
 )
 
+from .._plugin import (
+    load_signing_key,
+)
+
 from .strategies import (
     minimal_tahoe_configs,
     tahoe_configs,
@@ -147,6 +155,7 @@ from .strategies import (
     sharenum_sets,
     sizes,
     pass_counts,
+    ristretto_signing_keys,
 )
 from .matchers import (
     Provides,
@@ -662,3 +671,22 @@ class LeaseMaintenanceServiceTests(TestCase):
         been written to the client's configuration directory.
         """
         return self._created_test(get_config, servers_yaml, rootcap=False)
+
+
+class LoadSigningKeyTests(TestCase):
+    """
+    Tests for ``load_signing_key``.
+    """
+    @given(ristretto_signing_keys())
+    def test_valid(self, key_bytes):
+        """
+        A base64-encoded byte string representing a valid Ristretto signing key
+        can be loaded from a file into a ``SigningKey`` object using
+        ``load_signing_key``.
+
+        :param bytes key: A base64-encoded Ristretto signing key.
+        """
+        p = FilePath(self.useFixture(TempDir()).join(b"key"))
+        p.setContent(key_bytes)
+        key = load_signing_key(p)
+        self.assertThat(key, IsInstance(SigningKey))
diff --git a/src/_zkapauthorizer/tests/testing-signing.key b/src/_zkapauthorizer/tests/testing-signing.key
index 078e2134bd719832938b965b0288e4246c364d2d..829dff84ace4eb48281135c5f4e2a63b2bd79879 100644
--- a/src/_zkapauthorizer/tests/testing-signing.key
+++ b/src/_zkapauthorizer/tests/testing-signing.key
@@ -1 +1,2 @@
-mkQf85V2vyLQRUYuqRb+Ke6K+M9pOtXm4MslsuCdBgg=
\ No newline at end of file
+mkQf85V2vyLQRUYuqRb+Ke6K+M9pOtXm4MslsuCdBgg= 
+