diff --git a/README.md b/README.md
index 77e15fe8..71ce7496 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,9 @@ details as to what they are for. As an overview:
 - [`infra/`](./infra) contains the configurations for the various VMs that are
   in production for the project, for instance the Git instances or the Wiki.
 
+- [`keys/`](./keys) contains the public keys of the contributors to this project
+  as well as the systems that we administrate.
+
 - [`matrix/`](./matrix) contains everything having to do with setting up a
   fully-featured Matrix server.
 
diff --git a/flake.nix b/flake.nix
index 4cdf14e6..6c4f9c24 100644
--- a/flake.nix
+++ b/flake.nix
@@ -48,6 +48,7 @@
               optin = [
                 "deployment"
                 "infra"
+                "keys"
                 "secrets"
                 "services"
               ];
diff --git a/keys/contributors/niols b/keys/contributors/niols
new file mode 100644
index 00000000..d8d94759
--- /dev/null
+++ b/keys/contributors/niols
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEElREJN0AC7lbp+5X204pQ5r030IbgCllsIxyU3iiKY niols@wallace
diff --git a/keys/default.nix b/keys/default.nix
new file mode 100644
index 00000000..3bc08d14
--- /dev/null
+++ b/keys/default.nix
@@ -0,0 +1,16 @@
+let
+  inherit (builtins)
+    elemAt
+    mapAttrs
+    match
+    readDir
+    readFile
+    ;
+  removeTrailingWhitespace = s: elemAt (match "(.*[^[:space:]])[[:space:]]*" s) 0;
+  collectKeys =
+    dir: mapAttrs (name: _: removeTrailingWhitespace (readFile (dir + "/${name}"))) (readDir dir);
+in
+{
+  contributors = collectKeys ./contributors;
+  systems = collectKeys ./systems;
+}
diff --git a/keys/systems/vm02116 b/keys/systems/vm02116
new file mode 100644
index 00000000..95d0e9de
--- /dev/null
+++ b/keys/systems/vm02116
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILriawl1za2jbxzelkL5v8KPmcvuj7xVBgwFxuM/zhYr
diff --git a/keys/systems/vm02179 b/keys/systems/vm02179
new file mode 100644
index 00000000..d3e0494f
--- /dev/null
+++ b/keys/systems/vm02179
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPAsOCOsJ0vNL9fGj0XC25ir8B+k2NlVJzsiVUx+0eWM
diff --git a/keys/systems/vm02186 b/keys/systems/vm02186
new file mode 100644
index 00000000..a3a78b64
--- /dev/null
+++ b/keys/systems/vm02186
@@ -0,0 +1 @@
+ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII6mnBgEeyYE4tzHeFNHVNBV6KR+hAqh3PYSqlh0QViW
diff --git a/secrets/secrets.nix b/secrets/secrets.nix
index 54a86bce..21904811 100644
--- a/secrets/secrets.nix
+++ b/secrets/secrets.nix
@@ -1,46 +1,32 @@
 let
   pkgs = import <nixpkgs> { system = builtins.currentSystem; };
+  inherit (builtins) attrValues;
   inherit (pkgs.lib.attrsets) concatMapAttrs;
 
-  ##############################################################################
-  ## Contributor personal keys
-  ##
-  ## All the contributors in this list WILL be able to decrypt ALL the encrypted
-  ## `.age` files.
-
-  contributors = [
-    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEElREJN0AC7lbp+5X204pQ5r030IbgCllsIxyU3iiKY niols@wallace"
-  ];
-
-  ##############################################################################
-  ## System host keys
-  ##
-  ## Machines in this list MAY be mentioned later on as able to decrypt some of
-  ## the encrypted `.age` files.
-
-  vm02116 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILriawl1za2jbxzelkL5v8KPmcvuj7xVBgwFxuM/zhYr";
-  vm02179 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPAsOCOsJ0vNL9fGj0XC25ir8B+k2NlVJzsiVUx+0eWM";
-  vm02186 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAII6mnBgEeyYE4tzHeFNHVNBV6KR+hAqh3PYSqlh0QViW";
-
-  ##############################################################################
-
+  keys = import ../keys;
+  contributors = attrValues keys.contributors;
 in
+
 concatMapAttrs
-  (name: keys: {
-    "${name}.age".publicKeys = contributors ++ keys;
+  (name: systems: {
+    "${name}.age".publicKeys = contributors ++ systems;
   })
 
-  ##############################################################################
-  ## File name <-> system host keys mapping
-  ##
-  ## This attribute set defines precisely which secrets exist and which systems
-  ## are able to decrypt them.
+  (
+    with keys.systems;
 
-  {
-    forgejo-database-password = [ vm02116 ];
-    forgejo-email-password = [ vm02116 ];
-    forgejo-runner-token = [
-      vm02179
-      vm02186
-    ];
-  }
+    ##############################################################################
+    ## File name <-> system host keys mapping
+    ##
+    ## This attribute set defines precisely which secrets exist and which systems
+    ## are able to decrypt them.
+
+    {
+      forgejo-database-password = [ vm02116 ];
+      forgejo-email-password = [ vm02116 ];
+      forgejo-runner-token = [
+        vm02179
+        vm02186
+      ];
+    }
+  )