diff --git a/keys/settings.py b/keys/settings.py
index e8e8cc56cd2dd5a3ca1aa99b6010a299010bd454..e2be4f2437c9b7dd67dfb909024135d882cc0922 100644
--- a/keys/settings.py
+++ b/keys/settings.py
@@ -39,6 +39,7 @@ INSTALLED_APPS = [
     'django.contrib.staticfiles',
     'owlca',
     'pgp',
+    'ssh',
     'guardian',
 ]
 
@@ -63,7 +64,7 @@ ROOT_URLCONF = 'keys.urls'
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
+        'DIRS': ["keys/templates"],
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [
diff --git a/keys/templates/keys/base.html b/keys/templates/keys/base.html
new file mode 100644
index 0000000000000000000000000000000000000000..e96dcda7c11c96d38ad542b3d10f82256940acfc
--- /dev/null
+++ b/keys/templates/keys/base.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <meta charset="utf-8" />
+  <title>Sauerburger Keys</title>
+</head>
+<body>
+<nav>
+Nav
+</nav>
+<main>
+{% block content %}
+{% endblock %}
+</main>
+</body>
+</html>
diff --git a/keys/urls.py b/keys/urls.py
index dc8a3c4971e9f830b376e0151ad7c06ab654db1f..7acaa720c004b07f82b9237fa8b324a8af1194e0 100644
--- a/keys/urls.py
+++ b/keys/urls.py
@@ -19,5 +19,6 @@ from django.urls import path, include
 urlpatterns = [
     path('pki/', include('owlca.urls')),
     path('pgp/', include('pgp.urls')),
+    path('ssh/', include('ssh.urls')),
     path('admin/', admin.site.urls),
 ]
diff --git a/ssh/__init__.py b/ssh/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/ssh/admin.py b/ssh/admin.py
new file mode 100644
index 0000000000000000000000000000000000000000..0bb99ca96aa10a204231ab4b71b86f468b0d81b3
--- /dev/null
+++ b/ssh/admin.py
@@ -0,0 +1,4 @@
+from django.contrib import admin
+from . import models
+
+admin.site.register(models.SSHPublicKey)
diff --git a/ssh/apps.py b/ssh/apps.py
new file mode 100644
index 0000000000000000000000000000000000000000..06aa1aa568b73a664cf93b823502273f68fb94a6
--- /dev/null
+++ b/ssh/apps.py
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class SshConfig(AppConfig):
+    name = 'ssh'
diff --git a/ssh/migrations/0001_initial.py b/ssh/migrations/0001_initial.py
new file mode 100644
index 0000000000000000000000000000000000000000..939e57ed6bd1b836052355068b59cb7e02ecb4b3
--- /dev/null
+++ b/ssh/migrations/0001_initial.py
@@ -0,0 +1,25 @@
+# Generated by Django 3.1.3 on 2021-02-19 14:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SSHPublicKey',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('hostname', models.CharField(max_length=128)),
+                ('post', models.IntegerField(blank=True, null=True)),
+                ('keytype', models.CharField(max_length=32)),
+                ('key', models.TextField()),
+                ('created', models.DateTimeField(auto_now_add=True)),
+            ],
+        ),
+    ]
diff --git a/ssh/migrations/0002_auto_20210219_1432.py b/ssh/migrations/0002_auto_20210219_1432.py
new file mode 100644
index 0000000000000000000000000000000000000000..005daa3b8ba719a8bf3f1241d2a1cd793976cb0e
--- /dev/null
+++ b/ssh/migrations/0002_auto_20210219_1432.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.3 on 2021-02-19 14:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ssh', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='sshpublickey',
+            old_name='post',
+            new_name='port',
+        ),
+    ]
diff --git a/ssh/migrations/0003_sshpublickey_client_cert.py b/ssh/migrations/0003_sshpublickey_client_cert.py
new file mode 100644
index 0000000000000000000000000000000000000000..9ac12e23b2af16ed351194af0eebf01785b767af
--- /dev/null
+++ b/ssh/migrations/0003_sshpublickey_client_cert.py
@@ -0,0 +1,18 @@
+# Generated by Django 3.1.3 on 2021-02-19 22:42
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('ssh', '0002_auto_20210219_1432'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='sshpublickey',
+            name='client_cert',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/ssh/migrations/__init__.py b/ssh/migrations/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/ssh/models.py b/ssh/models.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5635ceb4f335ed96e3a588d195af0b72f56a1e3
--- /dev/null
+++ b/ssh/models.py
@@ -0,0 +1,29 @@
+from base64 import b64encode, b64decode
+import hashlib
+from django.db import models
+
+class SSHPublicKey(models.Model):
+    hostname = models.CharField(max_length=128)
+    port = models.IntegerField(null=True, blank=True)
+    keytype = models.CharField(max_length=32)
+    key = models.TextField()
+    created = models.DateTimeField(auto_now_add=True)
+    client_cert = models.BooleanField(default=False)
+
+    def server(self):
+        if self.port:
+            return f"{self.hostname}:{self.port}"
+        return self.hostname
+
+    def __str__(self):
+        return f"{self.hostname} ({self.keytype})"
+
+    def fingerprint_sha256(self):
+        raw_key = b64decode(self.key)
+        digest = b64encode(hashlib.sha256(raw_key).digest())
+        return digest.decode().replace("=", "")
+
+    def fingerprint_md5(self):
+        raw_key = b64decode(self.key)
+        digest = hashlib.md5(raw_key).hexdigest()
+        return ":".join(a + b for a, b in zip(digest[::2], digest[1::2]))
diff --git a/ssh/templates/ssh/sshpublickey_list.html b/ssh/templates/ssh/sshpublickey_list.html
new file mode 100644
index 0000000000000000000000000000000000000000..478fd5743d6671cd3ad3ebec54c9be26982e357c
--- /dev/null
+++ b/ssh/templates/ssh/sshpublickey_list.html
@@ -0,0 +1,17 @@
+{% extends 'keys/base.html' %}
+
+{% block content %}
+{% if sshpublickey_list %}
+<ul>
+{% for publickey in sshpublickey_list %}
+    <li>{{ publickey.server }}:<br />
+        SHA256: {{ publickey.fingerprint_sha256 }}<br /> 
+        MD5: {{ publickey.fingerprint_md5 }}<br /> 
+        <pre>{{ publickey.keytype }} {{ publickey.key }} {{ publickey.hostname }}</pre>
+    </li>
+{% endfor %}
+</ul>
+{% else %}
+<p>There are not public keys.</p>
+{% endif %}
+{% endblock %}
diff --git a/ssh/tests.py b/ssh/tests.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ce503c2dd97ba78597f6ff6e4393132753573f6
--- /dev/null
+++ b/ssh/tests.py
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/ssh/urls.py b/ssh/urls.py
new file mode 100644
index 0000000000000000000000000000000000000000..409a87280651c41cdd4e45d18c2f8d7d41cd3b20
--- /dev/null
+++ b/ssh/urls.py
@@ -0,0 +1,9 @@
+
+from django.urls import path
+
+from . import views
+
+urlpatterns = [
+    path("", views.PublicKeyListView.as_view(), name="ssh-list"),
+    path("<int:pk>", views.PublicKeyDetailView.as_view(), name="ssh-detail"),
+]
diff --git a/ssh/views.py b/ssh/views.py
new file mode 100644
index 0000000000000000000000000000000000000000..9725af443f195cc766bf2402e73b6fa5f0a094e1
--- /dev/null
+++ b/ssh/views.py
@@ -0,0 +1,14 @@
+from django.shortcuts import render
+from django.views.generic import DetailView, ListView, CreateView
+from guardian.mixins import PermissionRequiredMixin, PermissionListMixin
+from . import models
+
+
+class PublicKeyListView(PermissionListMixin, ListView):
+    model = models.SSHPublicKey
+    permission_required = ['view_sshpublickey']
+
+class PublicKeyDetailView(PermissionRequiredMixin, DetailView):
+    model = models.SSHPublicKey
+    permission_required = ['view_sshpublickey']
+