diff --git a/cmd/burrow/main.go b/cmd/burrow/main.go
index 6fb5ee54db1b4eb647c56175ee9283cd0e2de959..d1fa13f458c656e2ee27d8a5b00f23116e397d4b 100644
--- a/cmd/burrow/main.go
+++ b/cmd/burrow/main.go
@@ -14,6 +14,7 @@ import (
 	"github.com/hyperledger/burrow/genesis"
 	"github.com/hyperledger/burrow/genesis/spec"
 	"github.com/hyperledger/burrow/keys"
+	"github.com/hyperledger/burrow/keys/mock"
 	"github.com/hyperledger/burrow/logging"
 	logging_config "github.com/hyperledger/burrow/logging/config"
 	"github.com/hyperledger/burrow/logging/config/presets"
@@ -144,6 +145,15 @@ func main() {
 
 			genesisDocOpt := cmd.StringOpt("g genesis-doc", "", "GenesisDoc in JSON or TOML to embed in config")
 
+			generateKeysOpt := cmd.StringOpt("x generate-keys", "",
+				"File to output containing secret keys as JSON or according to a custom template (see --keys-template). "+
+					"Note that using this options means the keys will not be generated in the default keys instance")
+
+			keysTemplateOpt := cmd.StringOpt("z keys-template", mock.DefaultDumpKeysFormat,
+				fmt.Sprintf("Go text/template template (left delim: %s right delim: %s) to generate secret keys "+
+					"file specified with --generate-keys. Default:\n%s", mock.LeftTemplateDelim, mock.RightTemplateDelim,
+					mock.DefaultDumpKeysFormat))
+
 			separateGenesisDoc := cmd.StringOpt("s separate-genesis-doc", "", "Emit a separate genesis doc as JSON or TOML")
 
 			validatorIndexOpt := cmd.IntOpt("v validator-index", -1,
@@ -162,7 +172,8 @@ func main() {
 
 			chainNameOpt := cmd.StringOpt("n chain-name", "", "Default chain name")
 
-			cmd.Spec = "[--keys-url=<keys URL>] [--genesis-spec=<GenesisSpec file> | --genesis-doc=<GenesisDoc file>] " +
+			cmd.Spec = "[--keys-url=<keys URL> | (--generate-keys=<secret keys files> [--keys-template=<text template for each key>])] " +
+				"[--genesis-spec=<GenesisSpec file> | --genesis-doc=<GenesisDoc file>] " +
 				"[--separate-genesis-doc=<genesis JSON file>] [--validator-index=<index>] [--chain-name] [--json-out] " +
 				"[--logging=<logging program>] [--describe-logging] [--debug]"
 
@@ -192,14 +203,28 @@ func main() {
 					conf.Keys.URL = *keysUrlOpt
 				}
 
+				// Genesis Spec
 				if *genesisSpecOpt != "" {
 					genesisSpec := new(spec.GenesisSpec)
 					err := source.FromFile(*genesisSpecOpt, genesisSpec)
 					if err != nil {
 						fatalf("could not read GenesisSpec: %v", err)
 					}
-					keyClient := keys.NewKeyClient(conf.Keys.URL, logging.NewNoopLogger())
-					conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient)
+					if *generateKeysOpt != "" {
+						keyClient := mock.NewMockKeyClient()
+						conf.GenesisDoc, err = genesisSpec.GenesisDoc(keyClient)
+
+						secretKeysString, err := keyClient.DumpKeys(*keysTemplateOpt)
+						if err != nil {
+							fatalf("Could not dump keys: %v", err)
+						}
+						err = ioutil.WriteFile(*generateKeysOpt, []byte(secretKeysString), 0700)
+						if err != nil {
+							fatalf("Could not write secret keys: %v", err)
+						}
+					} else {
+						conf.GenesisDoc, err = genesisSpec.GenesisDoc(keys.NewKeyClient(conf.Keys.URL, logging.NewNoopLogger()))
+					}
 					if err != nil {
 						fatalf("could not realise GenesisSpec: %v", err)
 					}
@@ -212,6 +237,7 @@ func main() {
 					conf.GenesisDoc = genesisDoc
 				}
 
+				// Which validator am I?
 				if *validatorIndexOpt > -1 {
 					if conf.GenesisDoc == nil {
 						fatalf("Unable to set ValidatorAddress from provided validator-index since no " +
@@ -227,6 +253,7 @@ func main() {
 					conf.ValidatorAddress = &conf.GenesisDoc.Validators[0].Address
 				}
 
+				// Logging
 				if *loggingOpt != "" {
 					ops := strings.Split(*loggingOpt, ",")
 					sinkConfig, err := presets.BuildSinkConfig(ops...)
diff --git a/keys/mock/key_client_mock.go b/keys/mock/key_client_mock.go
index 96a614cac83ebe8367a84e76fad91563a1198375..1faa65e269e5ed2fb4112f41c5adfd60e6ae8076 100644
--- a/keys/mock/key_client_mock.go
+++ b/keys/mock/key_client_mock.go
@@ -18,11 +18,18 @@ import (
 	"crypto/rand"
 	"fmt"
 
+	"bytes"
+	"text/template"
+
+	"encoding/base64"
+
 	acm "github.com/hyperledger/burrow/account"
 	. "github.com/hyperledger/burrow/keys"
 	"github.com/tendermint/ed25519"
 	crypto "github.com/tendermint/go-crypto"
+	"github.com/tmthrgd/go-hex"
 	"golang.org/x/crypto/ripemd160"
+	"github.com/pkg/errors"
 )
 
 //---------------------------------------------------------------------
@@ -30,14 +37,35 @@ import (
 
 // Simple ed25519 key structure for mock purposes with ripemd160 address
 type MockKey struct {
+	Name       string
 	Address    acm.Address
-	PrivateKey [ed25519.PrivateKeySize]byte
 	PublicKey  []byte
+	PrivateKey []byte
 }
 
-func newMockKey() (*MockKey, error) {
+const DefaultDumpKeysFormat = `{
+  "Keys": [<< range $index, $key := . >><< if $index>>,<< end >>
+    {
+      "Name": "<< $key.Name >>",
+      "Address": "<< $key.Address >>",
+      "PublicKey": "<< $key.PublicKeyBase64 >>",
+      "PrivateKey": "<< $key.PrivateKeyBase64 >>"
+    }<< end >>
+  ]
+}`
+
+const LeftTemplateDelim = "<<"
+const RightTemplateDelim = ">>"
+
+var DefaultDumpKeysTemplate = template.Must(template.New("MockKeyClient_DumpKeys").
+	Delims(LeftTemplateDelim, RightTemplateDelim).
+	Parse(DefaultDumpKeysFormat))
+
+func newMockKey(name string) (*MockKey, error) {
 	key := &MockKey{
-		PublicKey: make([]byte, ed25519.PublicKeySize),
+		Name:       name,
+		PublicKey:  make([]byte, ed25519.PublicKeySize),
+		PrivateKey: make([]byte, ed25519.PrivateKeySize),
 	}
 	// this is a mock key, so the entropy of the source is purely
 	// for testing
@@ -65,15 +93,34 @@ func mockKeyFromPrivateAccount(privateAccount acm.PrivateAccount) *MockKey {
 		panic(fmt.Errorf("mock key client only supports ed25519 private keys at present"))
 	}
 	key := &MockKey{
-		Address:   privateAccount.Address(),
-		PublicKey: privateAccount.PublicKey().RawBytes(),
+		Name:       privateAccount.Address().String(),
+		Address:    privateAccount.Address(),
+		PublicKey:  privateAccount.PublicKey().RawBytes(),
+		PrivateKey: privateAccount.PrivateKey().RawBytes(),
 	}
-	copy(key.PrivateKey[:], privateAccount.PrivateKey().RawBytes())
 	return key
 }
 
 func (mockKey *MockKey) Sign(message []byte) (acm.Signature, error) {
-	return acm.SignatureFromBytes(ed25519.Sign(&mockKey.PrivateKey, message)[:])
+	var privateKey [ed25519.PrivateKeySize]byte
+	copy(privateKey[:], mockKey.PrivateKey)
+	return acm.SignatureFromBytes(ed25519.Sign(&privateKey, message)[:])
+}
+
+func (mockKey *MockKey) PrivateKeyBase64() string {
+	return base64.StdEncoding.EncodeToString(mockKey.PrivateKey[:])
+}
+
+func (mockKey *MockKey) PrivateKeyHex() string {
+	return hex.EncodeUpperToString(mockKey.PrivateKey[:])
+}
+
+func (mockKey *MockKey) PublicKeyBase64() string {
+	return base64.StdEncoding.EncodeToString(mockKey.PublicKey)
+}
+
+func (mockKey *MockKey) PublicKeyHex() string {
+	return hex.EncodeUpperToString(mockKey.PublicKey)
 }
 
 //---------------------------------------------------------------------
@@ -96,26 +143,26 @@ func NewMockKeyClient(privateAccounts ...acm.PrivateAccount) *MockKeyClient {
 	return client
 }
 
-func (mock *MockKeyClient) NewKey() acm.Address {
+func (mkc *MockKeyClient) NewKey(name string) acm.Address {
 	// Only tests ED25519 curve and ripemd160.
-	key, err := newMockKey()
+	key, err := newMockKey(name)
 	if err != nil {
 		panic(fmt.Sprintf("Mocked key client failed on key generation: %s", err))
 	}
-	mock.knownKeys[key.Address] = key
+	mkc.knownKeys[key.Address] = key
 	return key.Address
 }
 
-func (mock *MockKeyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) {
-	key := mock.knownKeys[signAddress]
+func (mkc *MockKeyClient) Sign(signAddress acm.Address, message []byte) (acm.Signature, error) {
+	key := mkc.knownKeys[signAddress]
 	if key == nil {
 		return acm.Signature{}, fmt.Errorf("Unknown address (%s)", signAddress)
 	}
 	return key.Sign(message)
 }
 
-func (mock *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) {
-	key := mock.knownKeys[address]
+func (mkc *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error) {
+	key := mkc.knownKeys[address]
 	if key == nil {
 		return acm.PublicKey{}, fmt.Errorf("Unknown address (%s)", address)
 	}
@@ -124,10 +171,27 @@ func (mock *MockKeyClient) PublicKey(address acm.Address) (acm.PublicKey, error)
 	return acm.PublicKeyFromGoCryptoPubKey(pubKeyEd25519.Wrap())
 }
 
-func (mock *MockKeyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) {
-	return mock.NewKey(), nil
+func (mkc *MockKeyClient) Generate(keyName string, keyType KeyType) (acm.Address, error) {
+	return mkc.NewKey(keyName), nil
 }
 
-func (mock *MockKeyClient) HealthCheck() error {
+func (mkc *MockKeyClient) HealthCheck() error {
 	return nil
 }
+
+func (mkc *MockKeyClient) DumpKeys(templateString string) (string, error) {
+	tmpl, err := template.New("DumpKeys").Delims(LeftTemplateDelim, RightTemplateDelim).Parse(templateString)
+	if err != nil {
+		errors.Wrap(err, "could not dump keys to template")
+	}
+	buf := new(bytes.Buffer)
+	keys := make([]*MockKey, 0, len(mkc.knownKeys))
+	for _, k := range mkc.knownKeys {
+		keys = append(keys, k)
+	}
+	err = tmpl.Execute(buf, keys)
+	if err != nil {
+		return "", err
+	}
+	return buf.String(), nil
+}
diff --git a/keys/mock/key_client_mock_test.go b/keys/mock/key_client_mock_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..ffcec0019c000eecfa116ec1be3e2b275cb6c992
--- /dev/null
+++ b/keys/mock/key_client_mock_test.go
@@ -0,0 +1,29 @@
+package mock
+
+import (
+	"testing"
+
+	"encoding/json"
+
+	"github.com/hyperledger/burrow/keys"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+func TestMockKeyClient_DumpKeys(t *testing.T) {
+	keyClient := NewMockKeyClient()
+	_, err := keyClient.Generate("foo", keys.KeyTypeEd25519Ripemd160)
+	require.NoError(t, err)
+	_, err = keyClient.Generate("foobar", keys.KeyTypeEd25519Ripemd160)
+	require.NoError(t, err)
+	dump, err := keyClient.DumpKeys(DefaultDumpKeysFormat)
+	require.NoError(t, err)
+
+	// Check JSON equal
+	var keys struct{ Keys []*MockKey }
+	err = json.Unmarshal([]byte(dump), &keys)
+	require.NoError(t, err)
+	bs, err := json.MarshalIndent(keys, "", "  ")
+	require.NoError(t, err)
+	assert.Equal(t, string(bs), dump)
+}