diff --git a/commands/user_key.go b/commands/user_key.go
index 0cc8681fbcc8f84a15e5f2a6fb06d36832e1cb71..05892f53f8eedb1bb43a6cf5f3dd8c1d468c1561 100644
--- a/commands/user_key.go
+++ b/commands/user_key.go
@@ -3,10 +3,10 @@ package commands
 import (
 	"fmt"
 
+	"github.com/spf13/cobra"
+
 	"github.com/MichaelMure/git-bug/cache"
-	"github.com/MichaelMure/git-bug/identity"
 	"github.com/MichaelMure/git-bug/util/interrupt"
-	"github.com/spf13/cobra"
 )
 
 func ResolveUser(repo *cache.RepoCache, args []string) (*cache.IdentityCache, []string, error) {
@@ -39,11 +39,7 @@ func runKey(cmd *cobra.Command, args []string) error {
 	}
 
 	for _, key := range id.Keys() {
-		pubkey, err := key.GetPublicKey()
-		if err != nil {
-			return err
-		}
-		fmt.Println(identity.EncodeKeyFingerprint(pubkey.Fingerprint))
+		fmt.Println(key.Fingerprint())
 	}
 
 	return nil
diff --git a/commands/user_key_add.go b/commands/user_key_add.go
index 3699ca336569b779ef122d7704d8942ecf0785ed..72e0ca24a4b1d480a42a96a808b7b2617e5ecf68 100644
--- a/commands/user_key_add.go
+++ b/commands/user_key_add.go
@@ -13,8 +13,8 @@ import (
 )
 
 var (
-	keyAddArmoredFile    string
-	keyAddArmored        string
+	keyAddArmoredFile string
+	keyAddArmored     string
 )
 
 func runKeyAdd(cmd *cobra.Command, args []string) error {
@@ -52,7 +52,7 @@ func runKeyAdd(cmd *cobra.Command, args []string) error {
 		}
 	}
 
-	key, err := identity.NewKey(keyAddArmored)
+	key, err := identity.NewKeyFromArmored(keyAddArmored)
 	if err != nil {
 		return err
 	}
@@ -61,9 +61,9 @@ func runKeyAdd(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return errors.Wrap(err, "failed to create validator")
 	}
-	commitHash := validator.KeyCommitHash(key.PublicKey.KeyId)
+	commitHash := validator.KeyCommitHash(key.publicKey().KeyId)
 	if commitHash != "" {
-		return fmt.Errorf("key id %d is already used by the key introduced in commit %s", key.PublicKey.KeyId, commitHash)
+		return fmt.Errorf("key id %d is already used by the key introduced in commit %s", key.publicKey.KeyId, commitHash)
 	}
 
 	err = id.Mutate(func(mutator identity.Mutator) identity.Mutator {
diff --git a/commands/user_key_rm.go b/commands/user_key_rm.go
index f35d0a89394ea7bde87add04ca4480649e1405fc..ff581cde05622763854a8d817b96e7016953e8c2 100644
--- a/commands/user_key_rm.go
+++ b/commands/user_key_rm.go
@@ -3,11 +3,12 @@ package commands
 import (
 	"fmt"
 
+	"github.com/pkg/errors"
+	"github.com/spf13/cobra"
+
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/identity"
 	"github.com/MichaelMure/git-bug/util/interrupt"
-	"github.com/pkg/errors"
-	"github.com/spf13/cobra"
 )
 
 func runKeyRm(cmd *cobra.Command, args []string) error {
@@ -22,7 +23,7 @@ func runKeyRm(cmd *cobra.Command, args []string) error {
 		return errors.New("missing key fingerprint")
 	}
 
-	keyFingerprint := args[0]
+	fingerprint := args[0]
 	args = args[1:]
 
 	id, args, err := ResolveUser(backend, args)
@@ -34,21 +35,10 @@ func runKeyRm(cmd *cobra.Command, args []string) error {
 		return fmt.Errorf("unexpected arguments: %s", args)
 	}
 
-	fingerprint, err := identity.DecodeKeyFingerprint(keyFingerprint)
-	if err != nil {
-		return errors.Wrap(err, "invalid key fingerprint")
-	}
-
 	var removedKey *identity.Key
 	err = id.Mutate(func(mutator identity.Mutator) identity.Mutator {
 		for j, key := range mutator.Keys {
-			pubkey, err := key.GetPublicKey()
-			if err != nil {
-				fmt.Printf("Warning: failed to decode public key: %s", err)
-				continue
-			}
-
-			if pubkey.Fingerprint == fingerprint {
+			if key.Fingerprint() == fingerprint {
 				removedKey = key
 				copy(mutator.Keys[j:], mutator.Keys[j+1:])
 				mutator.Keys = mutator.Keys[:len(mutator.Keys)-1]
diff --git a/commands/validate.go b/commands/validate.go
index 6e4a43c52e1ebc13354d93639e61d270fdad365f..99de0e4316e6c3bdcf160a18c0b5cd575335bbda 100644
--- a/commands/validate.go
+++ b/commands/validate.go
@@ -24,7 +24,7 @@ func runValidate(cmd *cobra.Command, args []string) error {
 		return err
 	}
 
-	fmt.Printf("first commit signed with key: %s\n", identity.EncodeKeyFingerprint(validator.FirstKey.PublicKey.Fingerprint))
+	fmt.Printf("first commit signed with key: %s\n", identity.encodeKeyFingerprint(validator.FirstKey.publicKey.Fingerprint))
 
 	var refErr error
 	for _, ref := range args {
diff --git a/identity/identity_test.go b/identity/identity_test.go
index 05b5de2b053fb2a213765ea0561ad4323d8c3248..1d437f055f37c85620337576a6a433c8ffe649c8 100644
--- a/identity/identity_test.go
+++ b/identity/identity_test.go
@@ -45,7 +45,7 @@ func TestIdentityCommitLoad(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: repository.CreatePubkey(t)},
+					{armoredPublicKey: repository.CreatePubkey(t)},
 				},
 			},
 			{
@@ -53,7 +53,7 @@ func TestIdentityCommitLoad(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: repository.CreatePubkey(t)},
+					{armoredPublicKey: repository.CreatePubkey(t)},
 				},
 			},
 			{
@@ -61,7 +61,7 @@ func TestIdentityCommitLoad(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: repository.CreatePubkey(t)},
+					{armoredPublicKey: repository.CreatePubkey(t)},
 				},
 			},
 		},
@@ -90,7 +90,7 @@ func TestIdentityCommitLoad(t *testing.T) {
 		name:  "René Descartes",
 		email: "rene.descartes@example.com",
 		keys: []*Key{
-			{ArmoredPublicKey: repository.CreatePubkey(t)},
+			{armoredPublicKey: repository.CreatePubkey(t)},
 		},
 	})
 
@@ -99,7 +99,7 @@ func TestIdentityCommitLoad(t *testing.T) {
 		name:  "René Descartes",
 		email: "rene.descartes@example.com",
 		keys: []*Key{
-			{ArmoredPublicKey: repository.CreatePubkey(t)},
+			{armoredPublicKey: repository.CreatePubkey(t)},
 		},
 	})
 
@@ -118,7 +118,7 @@ func TestIdentityCommitLoad(t *testing.T) {
 func loadKeys(identity *Identity) {
 	for _, v := range identity.versions {
 		for _, k := range v.keys {
-			k.GetPublicKey()
+			k.publicKey()
 		}
 	}
 }
@@ -139,7 +139,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: "pubkeyA"},
+					{armoredPublicKey: "pubkeyA"},
 				},
 			},
 			{
@@ -147,7 +147,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: "pubkeyB"},
+					{armoredPublicKey: "pubkeyB"},
 				},
 			},
 			{
@@ -155,7 +155,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: "pubkeyC"},
+					{armoredPublicKey: "pubkeyC"},
 				},
 			},
 			{
@@ -163,7 +163,7 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: "pubkeyD"},
+					{armoredPublicKey: "pubkeyD"},
 				},
 			},
 			{
@@ -171,20 +171,20 @@ func TestIdentity_ValidKeysAtTime(t *testing.T) {
 				name:  "René Descartes",
 				email: "rene.descartes@example.com",
 				keys: []*Key{
-					{ArmoredPublicKey: "pubkeyE"},
+					{armoredPublicKey: "pubkeyE"},
 				},
 			},
 		},
 	}
 
 	assert.Nil(t, identity.ValidKeysAtTime(10))
-	assert.Equal(t, identity.ValidKeysAtTime(100), []*Key{{ArmoredPublicKey: "pubkeyA"}})
-	assert.Equal(t, identity.ValidKeysAtTime(140), []*Key{{ArmoredPublicKey: "pubkeyA"}})
-	assert.Equal(t, identity.ValidKeysAtTime(200), []*Key{{ArmoredPublicKey: "pubkeyB"}})
-	assert.Equal(t, identity.ValidKeysAtTime(201), []*Key{{ArmoredPublicKey: "pubkeyD"}})
-	assert.Equal(t, identity.ValidKeysAtTime(202), []*Key{{ArmoredPublicKey: "pubkeyD"}})
-	assert.Equal(t, identity.ValidKeysAtTime(300), []*Key{{ArmoredPublicKey: "pubkeyE"}})
-	assert.Equal(t, identity.ValidKeysAtTime(3000), []*Key{{ArmoredPublicKey: "pubkeyE"}})
+	assert.Equal(t, identity.ValidKeysAtTime(100), []*Key{{armoredPublicKey: "pubkeyA"}})
+	assert.Equal(t, identity.ValidKeysAtTime(140), []*Key{{armoredPublicKey: "pubkeyA"}})
+	assert.Equal(t, identity.ValidKeysAtTime(200), []*Key{{armoredPublicKey: "pubkeyB"}})
+	assert.Equal(t, identity.ValidKeysAtTime(201), []*Key{{armoredPublicKey: "pubkeyD"}})
+	assert.Equal(t, identity.ValidKeysAtTime(202), []*Key{{armoredPublicKey: "pubkeyD"}})
+	assert.Equal(t, identity.ValidKeysAtTime(300), []*Key{{armoredPublicKey: "pubkeyE"}})
+	assert.Equal(t, identity.ValidKeysAtTime(3000), []*Key{{armoredPublicKey: "pubkeyE"}})
 }
 
 // Test the immutable or mutable metadata search
diff --git a/identity/key.go b/identity/key.go
index 5be1e3bb8f0703eba67e4d609daca97aba9edd1b..f5d85d0e55b06950cb96418073c25663fce3694d 100644
--- a/identity/key.go
+++ b/identity/key.go
@@ -2,6 +2,7 @@ package identity
 
 import (
 	"encoding/hex"
+	"encoding/json"
 	"fmt"
 	"strings"
 
@@ -10,14 +11,20 @@ import (
 	"golang.org/x/crypto/openpgp/packet"
 )
 
+// Key hold a cryptographic public key
 type Key struct {
 	// PubKey is the armored PGP public key.
-	ArmoredPublicKey string `json:"pub_key"`
+	armoredPublicKey string
 
-	PublicKey *packet.PublicKey `json:"-"`
+	// memoized decoded public key
+	publicKey *packet.PublicKey
 }
 
-func NewKey(armoredPGPKey string) (*Key, error) {
+type keyJSON struct {
+	ArmoredPublicKey string `json:"armored_pub_key"`
+}
+
+func NewKeyFromArmored(armoredPGPKey string) (*Key, error) {
 	publicKey, err := parsePublicKey(armoredPGPKey)
 	if err != nil {
 		return nil, err
@@ -26,10 +33,70 @@ func NewKey(armoredPGPKey string) (*Key, error) {
 	return &Key{armoredPGPKey, publicKey}, nil
 }
 
+func (k *Key) MarshalJSON() ([]byte, error) {
+	return json.Marshal(keyJSON{
+		ArmoredPublicKey: k.armoredPublicKey,
+	})
+}
+
+func (k *Key) UnmarshalJSON(data []byte) error {
+	var aux keyJSON
+
+	if err := json.Unmarshal(data, &aux); err != nil {
+		return err
+	}
+
+	k.armoredPublicKey = aux.ArmoredPublicKey
+
+	return nil
+}
+
+func (k *Key) Validate() error {
+	if k.publicKey != nil {
+		return nil
+	}
+
+	publicKey, err := parsePublicKey(k.armoredPublicKey)
+	if err != nil {
+		return errors.Wrap(err, "invalid public key")
+	}
+	k.publicKey = publicKey
+
+	return nil
+}
+
+func (k *Key) Clone() *Key {
+	clone := *k
+	return &clone
+}
+
+func (k *Key) publicKey() *packet.PublicKey {
+	if k.publicKey != nil {
+		return k.publicKey
+	}
+
+	publicKey, err := parsePublicKey(k.armoredPublicKey)
+	if err != nil {
+		// Coding problem, a key should be validated before use
+		panic("invalid key: " + err.Error())
+	}
+
+	k.publicKey = publicKey
+	return k.publicKey
+}
+
+func (k Key) Armored() string {
+	return k.armoredPublicKey
+}
+
+func (k Key) Fingerprint() string {
+	return encodeKeyFingerprint(k.publicKey().Fingerprint)
+}
+
 func parsePublicKey(armoredPublicKey string) (*packet.PublicKey, error) {
 	block, err := armor.Decode(strings.NewReader(armoredPublicKey))
 	if err != nil {
-		return nil, errors.Wrap(err, "failed to dearmor public key")
+		return nil, errors.Wrap(err, "failed to decode armored public key")
 	}
 
 	reader := packet.NewReader(block.Body)
@@ -40,14 +107,14 @@ func parsePublicKey(armoredPublicKey string) (*packet.PublicKey, error) {
 
 	publicKey, ok := p.(*packet.PublicKey)
 	if !ok {
-		return nil, errors.New("got no packet.PublicKey")
+		return nil, errors.New("got no packet.publicKey")
 	}
 
 	return publicKey, nil
 }
 
-// DecodeKeyFingerprint decodes a 40 hex digits long fingerprint into bytes.
-func DecodeKeyFingerprint(keyFingerprint string) ([20]byte, error) {
+// decodeKeyFingerprint decodes a 40 hex digits long fingerprint into bytes.
+func decodeKeyFingerprint(keyFingerprint string) ([20]byte, error) {
 	var fingerprint [20]byte
 	fingerprintBytes, err := hex.DecodeString(keyFingerprint)
 	if err != nil {
@@ -60,24 +127,7 @@ func DecodeKeyFingerprint(keyFingerprint string) ([20]byte, error) {
 	return fingerprint, nil
 }
 
-func EncodeKeyFingerprint(fingerprint [20]byte) string {
+// encodeKeyFingerprint encode a byte representation of a fingerprint into a 40 hex digits string.
+func encodeKeyFingerprint(fingerprint [20]byte) string {
 	return strings.ToUpper(hex.EncodeToString(fingerprint[:]))
 }
-
-func (k *Key) Validate() error {
-	_, err := k.GetPublicKey()
-	return err
-}
-
-func (k *Key) Clone() *Key {
-	clone := *k
-	return &clone
-}
-
-func (k *Key) GetPublicKey() (*packet.PublicKey, error) {
-	var err error
-	if k.PublicKey == nil {
-		k.PublicKey, err = parsePublicKey(k.ArmoredPublicKey)
-	}
-	return k.PublicKey, err
-}
diff --git a/identity/key_test.go b/identity/key_test.go
index 7cb75e4c3f31092417a320823e3b7ed2c85d1500..d366545f4c1b7dda90a363125e7ab245a131b03d 100644
--- a/identity/key_test.go
+++ b/identity/key_test.go
@@ -1,9 +1,11 @@
 package identity
 
 import (
+	"encoding/json"
 	"strings"
 	"testing"
 
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 )
 
@@ -14,8 +16,45 @@ func TestDecodeKeyFingerprint(t *testing.T) {
 }
 
 func checkEncodeDecodeKeyFingerprint(t *testing.T, fingerprint string) {
-	decoded, err := DecodeKeyFingerprint(fingerprint)
+	decoded, err := decodeKeyFingerprint(fingerprint)
 	require.NoError(t, err)
-	require.Equal(t, fingerprint, EncodeKeyFingerprint(decoded))
+	require.Equal(t, fingerprint, encodeKeyFingerprint(decoded))
 }
 
+func TestKeySerialize(t *testing.T) {
+	armored := `-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mI0EXrVFzwEEAL7roW5pBs7PYhnW8XdHuMBUrOqx+TR8JPsTLzlFFKHniJ7Cxm24
+rj+nCiVAC3yI5hEWbLYLp6HoSCAEJim+ac+LsoH0Rxz325l+EYz7nAq44rebfNuy
+A5LD9/KVzLAu0FO27pgCiH9RpsFVYveHYtR1jDDvag6MLdlTZaQfqCGnABEBAAG0
+LFJlbsOpIERlc2NhcnRlcyA8cmVuZS5kZXNjYXJ0ZXNAZXhhbXBsZS5jb20+iM4E
+EwEKADgWIQQpwni46BlhwjZt/3bXoSG7jO2rwwUCXrVFzwIbAwULCQgHAgYVCgkI
+CwIEFgIDAQIeAQIXgAAKCRDXoSG7jO2rw5LcBACPp+2cwUcYCiHZVUAnAHokY7R0
+msjA/YryCy+rcW86TcV7KuyZjg3wCHi7DrDuvYIDXr83W2XaCoJktAW/+aENj8QH
+6r7Tini3ENmNT8caqiCJGE0iY0QRXZomxAoZc5kvDq596ifoUA08ILncGla7Uq04
++3Da+JBLWoDQvVP3xbiNBF61Rc8BBADBYKVgB1eHgXOorCeKYLCDSNwklkkdCN5u
+WZygmr/EMpT7YGuAvW70WKHcd0zo+MX/3fWvJeTQDVmReNF0zJv0OSjcAsamcOQ9
+G9rdL3bKWMGJRtTeXmtZ6BkP4YU227VkFTFXvQzt8WD5CXGQJtEZRXQqHKNjNNIY
+JUxF6VfJtQARAQABiLYEGAEKACAWIQQpwni46BlhwjZt/3bXoSG7jO2rwwUCXrVF
+zwIbDAAKCRDXoSG7jO2rw7xEA/9TJD/M6vO160zNr7fCB31rqGUvkHWOKaeSHJmG
+AvFBrNiBG+nGRjc2XbZqSaykO7ApcmLzgh8zzlB3gxZjorbEGRoEUsYD5pmZhfFl
+kZyE/aXEbuTIXXcR9fyuDGvP2eF4RPth8P4ew9ycXl89IUdbapD3JKg/ptkgw8dy
+y8TVdw==
+=01qL
+-----END PGP PUBLIC KEY BLOCK-----`
+
+	before, err := NewKeyFromArmored(armored)
+	assert.NoError(t, err)
+	assert.NoError(t, before.Validate())
+
+	data, err := json.Marshal(before)
+	assert.NoError(t, err)
+
+	var after Key
+	err = json.Unmarshal(data, &after)
+	assert.NoError(t, err)
+	assert.NoError(t, after.Validate())
+
+	assert.NotEmpty(t, after.Fingerprint())
+	assert.Equal(t, before.Fingerprint(), after.Fingerprint())
+}
diff --git a/identity/version_test.go b/identity/version_test.go
index dcfe73afc93f5371d8c83635353ffbec57bb2967..1734327a632934ae36dd48bd49d6e93e254e2e96 100644
--- a/identity/version_test.go
+++ b/identity/version_test.go
@@ -14,10 +14,10 @@ func TestVersionSerialize(t *testing.T) {
 		avatarURL: "avatarUrl",
 		keys: []*Key{
 			{
-				ArmoredPublicKey:      "pubkey1",
+				armoredPublicKey: "pubkey1",
 			},
 			{
-				ArmoredPublicKey:      "pubkey2",
+				armoredPublicKey: "pubkey2",
 			},
 		},
 		nonce: makeNonce(20),