diff --git a/services/identity/Cargo.lock b/services/identity/Cargo.lock
--- a/services/identity/Cargo.lock
+++ b/services/identity/Cargo.lock
@@ -60,6 +60,17 @@
  "syn",
 ]
 
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
 [[package]]
 name = "autocfg"
 version = "1.1.0"
@@ -129,6 +140,45 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "clap"
+version = "3.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "535434c063ced786eb04aaf529308092c5ab60889e8fe24275d15de07b01fa97"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_derive",
+ "clap_lex",
+ "indexmap",
+ "lazy_static",
+ "strsim",
+ "termcolor",
+ "textwrap",
+]
+
+[[package]]
+name = "clap_derive"
+version = "3.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
+dependencies = [
+ "heck 0.4.0",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
+dependencies = [
+ "os_str_bytes",
+]
+
 [[package]]
 name = "constant_time_eq"
 version = "0.1.5"
@@ -340,6 +390,12 @@
  "unicode-segmentation",
 ]
 
+[[package]]
+name = "heck"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+
 [[package]]
 name = "hermit-abi"
 version = "0.1.19"
@@ -444,6 +500,7 @@
 version = "0.1.0"
 dependencies = [
  "argon2",
+ "clap",
  "curve25519-dalek",
  "digest 0.9.0",
  "futures-core",
@@ -588,6 +645,12 @@
  "zeroize",
 ]
 
+[[package]]
+name = "os_str_bytes"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+
 [[package]]
 name = "password-hash"
 version = "0.3.2"
@@ -653,6 +716,30 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
 
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
 [[package]]
 name = "proc-macro2"
 version = "1.0.36"
@@ -679,7 +766,7 @@
 checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5"
 dependencies = [
  "bytes",
- "heck",
+ "heck 0.3.3",
  "itertools",
  "lazy_static",
  "log",
@@ -827,6 +914,12 @@
  "winapi",
 ]
 
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
 [[package]]
 name = "subtle"
 version = "2.4.1"
@@ -870,6 +963,21 @@
  "winapi",
 ]
 
+[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+
 [[package]]
 name = "tokio"
 version = "1.17.0"
@@ -1149,6 +1257,15 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
 
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
 [[package]]
 name = "winapi-x86_64-pc-windows-gnu"
 version = "0.4.0"
diff --git a/services/identity/Cargo.toml b/services/identity/Cargo.toml
--- a/services/identity/Cargo.toml
+++ b/services/identity/Cargo.toml
@@ -3,8 +3,6 @@
 version = "0.1.0"
 edition = "2021"
 
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
 [dependencies]
 tonic = "0.6"
 prost = "0.9"
@@ -15,6 +13,7 @@
 curve25519-dalek = "3"
 sha2 = "0.9"
 digest = "0.9"
+clap = { version = "3.1.12", features = ["derive"] }
 
 [build-dependencies]
 tonic-build = "0.6"
diff --git a/services/identity/Dockerfile b/services/identity/Dockerfile
--- a/services/identity/Dockerfile
+++ b/services/identity/Dockerfile
@@ -24,5 +24,6 @@
 RUN rm ./target/release/deps/identity*
 
 RUN cargo build --release
+RUN target/release/identity keygen
 
-CMD ["./target/release/identity"]
+CMD ["./target/release/identity", "server"]
diff --git a/services/identity/src/keygen.rs b/services/identity/src/keygen.rs
new file mode 100644
--- /dev/null
+++ b/services/identity/src/keygen.rs
@@ -0,0 +1,26 @@
+use crate::opaque::Cipher;
+use opaque_ke::{ciphersuite::CipherSuite, rand::rngs::OsRng};
+use std::{env, fs, io};
+
+const SECRETS_FILE_NAME: &str = "secret_key";
+const SECRETS_FILE_EXTENSION: &str = "txt";
+
+pub fn generate_and_persist_keypair(dir: &str) -> Result<(), io::Error> {
+  let mut rng = OsRng;
+  let server_kp = Cipher::generate_random_keypair(&mut rng);
+  let mut path = env::current_dir()?;
+  path.push(dir);
+  if !path.exists() {
+    println!("Creating secrets directory {:?}", path);
+    fs::create_dir(&path)?;
+  }
+  path.push(SECRETS_FILE_NAME);
+  path.set_extension(SECRETS_FILE_EXTENSION);
+  if path.exists() {
+    println!("{:?} already exists", path);
+    return Ok(());
+  }
+  println!("Writing secret key to {:?}", path);
+  fs::write(&path, server_kp.private().to_arr())?;
+  Ok(())
+}
diff --git a/services/identity/src/main.rs b/services/identity/src/main.rs
--- a/services/identity/src/main.rs
+++ b/services/identity/src/main.rs
@@ -1,19 +1,53 @@
+use clap::{Parser, Subcommand};
 use tonic::transport::Server;
 
+mod keygen;
+mod opaque;
 mod service;
+
+use keygen::generate_and_persist_keypair;
 use service::{IdentityServiceServer, MyIdentityService};
 
 const IDENTITY_SERVICE_SOCKET_ADDR: &str = "[::]:50051";
+const DEFAULT_SECRETS_DIRECTORY: &str = "secrets";
+
+#[derive(Parser)]
+#[clap(author, version, about, long_about = None)]
+#[clap(propagate_version = true)]
+struct Cli {
+  #[clap(subcommand)]
+  command: Commands,
+}
+
+#[derive(Subcommand)]
+enum Commands {
+  /// Runs the server
+  Server,
+  /// Generates and persists a keypair to use for PAKE registration and login
+  Keygen {
+    #[clap(short, long)]
+    #[clap(default_value_t = String::from(DEFAULT_SECRETS_DIRECTORY))]
+    dir: String,
+  },
+}
 
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
-  let addr = IDENTITY_SERVICE_SOCKET_ADDR.parse()?;
-  let identity_service = MyIdentityService::default();
+  let cli = Cli::parse();
+  match &cli.command {
+    Commands::Keygen { dir } => {
+      generate_and_persist_keypair(dir)?;
+    }
+    Commands::Server => {
+      let addr = IDENTITY_SERVICE_SOCKET_ADDR.parse()?;
+      let identity_service = MyIdentityService::default();
 
-  Server::builder()
-    .add_service(IdentityServiceServer::new(identity_service))
-    .serve(addr)
-    .await?;
+      Server::builder()
+        .add_service(IdentityServiceServer::new(identity_service))
+        .serve(addr)
+        .await?;
+    }
+  }
 
   Ok(())
 }