Add new command: `imgmod key show`

    show - Displays JSON describing one or more keys

    Usage:
      imgmod key show <key-file> [key-files...] [flags]
diff --git a/cli/key_cmds.go b/cli/key_cmds.go
new file mode 100644
index 0000000..4f6cf7a
--- /dev/null
+++ b/cli/key_cmds.go
@@ -0,0 +1,98 @@
+package cli
+
+import (
+	"crypto/sha256"
+	"encoding/hex"
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+
+	"github.com/apache/mynewt-artifact/errors"
+	"github.com/spf13/cobra"
+
+	"mynewt.apache.org/imgmod/ikey"
+)
+
+func keyDescToJson(path string, body []byte, desc ikey.Desc) (string, error) {
+	type Key struct {
+		Path       string `json:"path"`
+		Type       string `json:"type"`
+		Algorithm  string `json:"algorithm"`
+		Hash       string `json:"hash"`
+		FileSha256 string `json:"file_sha256"`
+	}
+
+	var typ string
+	if desc.Private {
+		typ = "private"
+	} else {
+		typ = "public"
+	}
+
+	h := sha256.Sum256(body)
+	fileHash := h[:]
+
+	k := Key{
+		Path:       path,
+		Type:       typ,
+		Algorithm:  desc.Algorithm,
+		Hash:       hex.EncodeToString(desc.Hash),
+		FileSha256: hex.EncodeToString(fileHash),
+	}
+
+	j, err := json.MarshalIndent(k, "", "    ")
+	if err != nil {
+		return "", errors.Wrapf(err,
+			"internal error: failed to marshal key description")
+	}
+
+	return string(j), nil
+}
+
+func runKeyShowCmd(cmd *cobra.Command, args []string) {
+	if len(args) < 1 {
+		ImgmodUsage(cmd, nil)
+	}
+
+	for i, arg := range args {
+		bin, err := ioutil.ReadFile(arg)
+		if err != nil {
+			ImgmodUsage(nil, err)
+		}
+
+		desc, err := ikey.KeyBytesToDesc(bin)
+		if err != nil {
+			ImgmodUsage(nil, errors.Wrapf(err, "file: \"%s\"", arg))
+		}
+
+		j, err := keyDescToJson(arg, bin, desc)
+		if err != nil {
+			ImgmodUsage(nil, err)
+		}
+
+		fmt.Printf("%s", j)
+		if i < len(args)-1 {
+			fmt.Printf(",")
+		}
+		fmt.Printf("\n")
+	}
+}
+
+func AddKeyCommands(cmd *cobra.Command) {
+	keyCmd := &cobra.Command{
+		Use:   "key",
+		Short: "Manipulates image keys",
+		Run: func(cmd *cobra.Command, args []string) {
+			cmd.Usage()
+		},
+	}
+	cmd.AddCommand(keyCmd)
+
+	showCmd := &cobra.Command{
+		Use:   "show <key-file> [key-files...]",
+		Short: "Displays JSON describing one or more keys",
+		Run:   runKeyShowCmd,
+	}
+
+	keyCmd.AddCommand(showCmd)
+}
diff --git a/ikey/ikey.go b/ikey/ikey.go
new file mode 100644
index 0000000..043d097
--- /dev/null
+++ b/ikey/ikey.go
@@ -0,0 +1,52 @@
+package ikey
+
+import (
+	"fmt"
+
+	"github.com/apache/mynewt-artifact/sec"
+	"github.com/pkg/errors"
+)
+
+type Desc struct {
+	Private   bool
+	Algorithm string
+	PubBytes  []byte
+	Hash      []byte
+}
+
+func signKeyToDesc(key sec.PubSignKey, private bool) (Desc, error) {
+	var alg string
+	if key.Rsa != nil {
+		alg = fmt.Sprintf("RSA-%d", key.Rsa.Size()*8)
+	} else if key.Ec != nil {
+		alg = fmt.Sprintf("ECDSA-%d", key.Ec.X.BitLen())
+	} else {
+		alg = "ED25519"
+	}
+
+	pubBytes, err := key.Bytes()
+	if err != nil {
+		return Desc{}, err
+	}
+
+	return Desc{
+		Private:   private,
+		Algorithm: alg,
+		PubBytes:  pubBytes,
+		Hash:      sec.RawKeyHash(pubBytes),
+	}, nil
+}
+
+func KeyBytesToDesc(keyBytes []byte) (Desc, error) {
+	pubsk, err := sec.ParsePubSignKey(keyBytes)
+	if err == nil {
+		return signKeyToDesc(pubsk, false)
+	}
+
+	privsk, err := sec.ParsePrivSignKey(keyBytes)
+	if err == nil {
+		return signKeyToDesc(privsk.PubKey(), true)
+	}
+
+	return Desc{}, errors.Errorf("unrecognized key type")
+}
diff --git a/imgmod.go b/imgmod.go
index 14f3692..b3e2bb0 100644
--- a/imgmod.go
+++ b/imgmod.go
@@ -75,6 +75,7 @@
 
 	cli.AddImageCommands(imgmodCmd)
 	cli.AddMfgCommands(imgmodCmd)
+	cli.AddKeyCommands(imgmodCmd)
 
 	imgmodCmd.Execute()
 }