From eff204faf5624c51b7ac96b9b93e4ce9622f853a Mon Sep 17 00:00:00 2001
From: Jared Hobbs <jared@pyhacker.com>
Date: Tue, 27 Nov 2018 17:22:59 -0700
Subject: [PATCH] add support for new OpenSSH private key format

This work is based off the work done in https://github.com/paramiko/paramiko/pull/618
---
 paramiko/dsskey.py      |  15 +++-
 paramiko/ecdsakey.py    |  22 +++--
 paramiko/ed25519key.py  |   4 +-
 paramiko/pkey.py        | 191 +++++++++++++++++++++++++++++++++++++---
 paramiko/rsakey.py      |  30 +++++--
 tests/test_dss_1k_o.key |  22 +++++
 tests/test_pkey.py      |  25 +++++-
 tests/test_rsa_2k_o.key |  28 ++++++
 9 files changed, 307 insertions(+), 31 deletions(-)
 create mode 100644 tests/test_dss_1k_o.key
 create mode 100644 tests/test_rsa_2k_o.key

diff --git a/paramiko/dsskey.py b/paramiko/dsskey.py
index ec358ee2..a1adf818 100644
--- a/paramiko/dsskey.py
+++ b/paramiko/dsskey.py
@@ -229,12 +229,19 @@ class DSSKey(PKey):
         self._decode_key(data)
 
     def _decode_key(self, data):
+        pkformat, data = data
         # private key file contains:
         # DSAPrivateKey = { version = 0, p, q, g, y, x }
-        try:
-            keylist = BER(data).decode()
-        except BERException as e:
-            raise SSHException("Unable to parse key file: " + str(e))
+        if pkformat == self.PRIVATE_KEY_FORMAT_ORIGINAL:
+            try:
+                keylist = BER(data).decode()
+            except BERException as e:
+                raise SSHException("Unable to parse key file: " + str(e))
+        elif pkformat == self.PRIVATE_KEY_FORMAT_OPENSSH:
+            keylist = self._uint32_cstruct_unpack(data, 'iiiii')
+            keylist = [0] + list(keylist)
+        else:
+            raise SSHException('private key format.')
         if type(keylist) is not list or len(keylist) < 6 or keylist[0] != 0:
             raise SSHException(
                 "not a valid DSA private key file (bad ber encoding)"
diff --git a/paramiko/ecdsakey.py b/paramiko/ecdsakey.py
index b73a969e..c3ef8e4d 100644
--- a/paramiko/ecdsakey.py
+++ b/paramiko/ecdsakey.py
@@ -283,12 +283,22 @@ class ECDSAKey(PKey):
         self._decode_key(data)
 
     def _decode_key(self, data):
-        try:
-            key = serialization.load_der_private_key(
-                data, password=None, backend=default_backend()
-            )
-        except (ValueError, AssertionError) as e:
-            raise SSHException(str(e))
+        pkformat, data = data
+        if pkformat == self.PRIVATE_KEY_FORMAT_ORIGINAL:
+            try:
+                key = serialization.load_der_private_key(
+                    data, password=None, backend=default_backend()
+                )
+            except (ValueError, AssertionError) as e:
+                raise SSHException(str(e))
+        elif pkformat == self.PRIVATE_KEY_FORMAT_OPENSSH:
+            curve, verkey, sigkey = self._uint32_cstruct_unpack(data, 'sss')
+            try:
+                key = ec.derive_private_key(sigkey, curve, default_backend())
+            except TypeError as e:
+                raise SSHException(str(e))
+        else:
+            raise SSHException('unknown private key format.')
 
         self.signing_key = key
         self.verifying_key = key.public_key()
diff --git a/paramiko/ed25519key.py b/paramiko/ed25519key.py
index 68ada224..3bd0ff10 100644
--- a/paramiko/ed25519key.py
+++ b/paramiko/ed25519key.py
@@ -73,9 +73,9 @@ class Ed25519Key(PKey):
             verifying_key = nacl.signing.VerifyKey(msg.get_binary())
         elif filename is not None:
             with open(filename, "r") as f:
-                data = self._read_private_key("OPENSSH", f)
+                pkformat, data = self._read_private_key("OPENSSH", f)
         elif file_obj is not None:
-            data = self._read_private_key("OPENSSH", file_obj)
+            pkformat, data = self._read_private_key("OPENSSH", file_obj)
 
         if filename or file_obj:
             signing_key = self._parse_signing_key_data(data, password)
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index fa014800..4e56233f 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -24,6 +24,10 @@ import base64
 from binascii import unhexlify
 import os
 from hashlib import md5
+import re
+import struct
+
+import bcrypt
 
 from cryptography.hazmat.backends import default_backend
 from cryptography.hazmat.primitives import serialization
@@ -31,7 +35,8 @@ from cryptography.hazmat.primitives.ciphers import algorithms, modes, Cipher
 
 from paramiko import util
 from paramiko.common import o600
-from paramiko.py3compat import u, encodebytes, decodebytes, b, string_types
+from paramiko.py3compat import u, encodebytes, decodebytes, b, string_types,\
+    byte_ord
 from paramiko.ssh_exception import SSHException, PasswordRequiredException
 from paramiko.message import Message
 
@@ -62,6 +67,12 @@ class PKey(object):
             "mode": modes.CBC,
         },
     }
+    PRIVATE_KEY_FORMAT_ORIGINAL = 1
+    PRIVATE_KEY_FORMAT_OPENSSH = 2
+    BEGIN_TAG = re.compile(
+        '^-{5}BEGIN (RSA|DSA|EC|OPENSSH) PRIVATE KEY-{5}\s*$'
+    )
+    END_TAG = re.compile('^-{5}END (RSA|DSA|EC|OPENSSH) PRIVATE KEY-{5}\s*$')
 
     def __init__(self, msg=None, data=None):
         """
@@ -281,29 +292,59 @@ class PKey(object):
 
     def _read_private_key(self, tag, f, password=None):
         lines = f.readlines()
+
+        # find the BEGIN tag
         start = 0
-        beginning_of_key = "-----BEGIN " + tag + " PRIVATE KEY-----"
-        while start < len(lines) and lines[start].strip() != beginning_of_key:
+        m = self.BEGIN_TAG.match(lines[start])
+        line_range = len(lines) - 1
+        while start < line_range and not m:
             start += 1
+            m = self.BEGIN_TAG.match(lines[start])
+        start += 1
+        keytype = m.group(1)
         if start >= len(lines):
             raise SSHException("not a valid " + tag + " private key file")
+
+        # find the END tag
+        end = start
+        m = self.END_TAG.match(lines[end])
+        while end < line_range and not m:
+            end += 1
+            m = self.END_TAG.match(lines[end])
+
+        if keytype == tag:
+            data = self._read_private_key_old_format(
+                lines,
+                password,
+            )
+            pkformat = self.PRIVATE_KEY_FORMAT_ORIGINAL
+        elif keytype == 'OPENSSH':
+            data = self._read_private_key_new_format(
+                lines[start:end],
+                password,
+            )
+            pkformat = self.PRIVATE_KEY_FORMAT_OPENSSH
+        else:
+            raise SSHException(
+                'encountered {} key, expected {} key'.format(keytype, tag)
+            )
+
+        return pkformat, data
+
+    def _read_private_key_old_format(self, lines, password):
+        start = 0
         # parse any headers first
         headers = {}
         start += 1
         while start < len(lines):
-            l = lines[start].split(": ")
-            if len(l) == 1:
+            line = lines[start].split(": ")
+            if len(line) == 1:
                 break
-            headers[l[0].lower()] = l[1].strip()
+            headers[line[0].lower()] = line[1].strip()
             start += 1
-        # find end
-        end = start
-        ending_of_key = "-----END " + tag + " PRIVATE KEY-----"
-        while end < len(lines) and lines[end].strip() != ending_of_key:
-            end += 1
         # if we trudged to the end of the file, just try to cope.
         try:
-            data = decodebytes(b("".join(lines[start:end])))
+            data = decodebytes(b(''.join(lines[start:])))
         except base64.binascii.Error as e:
             raise SSHException("base64 decoding error: " + str(e))
         if "proc-type" not in headers:
@@ -337,6 +378,132 @@ class PKey(object):
         ).decryptor()
         return decryptor.update(data) + decryptor.finalize()
 
+    def _read_private_key_new_format(self, lines, password):
+        """
+        Read the new OpenSSH SSH2 private key format available
+        since OpenSSH version 6.5
+        Reference:
+        https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
+        """
+        try:
+            data = decodebytes(b(''.join(lines)))
+        except base64.binascii.Error as e:
+            raise SSHException('base64 decoding error: ' + str(e))
+
+        # read data struct
+        auth_magic = data[:14]
+        if auth_magic != b('openssh-key-v1'):
+            raise SSHException('unexpected OpenSSH key header encountered')
+
+        cstruct = self._uint32_cstruct_unpack(data[15:], 'sssur')
+        cipher, kdfname, kdf_options, num_pubkeys, remainder = cstruct
+        # For now, just support 1 key.
+        if num_pubkeys > 1:
+            raise SSHException(
+                'unsupported: private keyfile has multiple keys'
+            )
+        pubkey, privkey_blob = self._uint32_cstruct_unpack(remainder, 'ss')
+
+        if kdfname == b('bcrypt'):
+            if cipher == b('aes256-cbc'):
+                mode = modes.CBC
+            elif cipher == b('aes256-ctr'):
+                mode = modes.CTR
+            else:
+                raise SSHException(
+                    'unknown cipher `{}` used in private key file'.format(
+                        cipher.decode('utf-8')
+                    )
+                )
+            # Encrypted private key.
+            # If no password was passed in, raise an exception pointing
+            # out that we need one
+            if password is None:
+                raise PasswordRequiredException(
+                    'private key file is encrypted'
+                )
+
+            # Unpack salt and rounds from kdfoptions
+            salt, rounds = self._uint32_cstruct_unpack(kdf_options, 'su')
+
+            # run bcrypt kdf to derive key and iv/nonce (32 + 16 bytes)
+            key_iv = bcrypt.kdf(b(password), b(salt), 48, rounds)
+            key = key_iv[:32]
+            iv = key_iv[32:]
+
+            # decrypt private key blob
+            decryptor = Cipher(
+                algorithms.AES(key), mode(iv), default_backend()
+            ).decryptor()
+            decrypted_privkey = decryptor.update(privkey_blob)
+            decrypted_privkey += decryptor.finalize()
+        elif cipher == b('none') and kdfname == b('none'):
+            # Unencrypted private key
+            decrypted_privkey = privkey_blob
+        else:
+            raise SSHException(
+                'unknown cipher or kdf used in private key file'
+            )
+
+        # Unpack private key and verify checkints
+        cstruct = self._uint32_cstruct_unpack(decrypted_privkey, 'uusr')
+        checkint1, checkint2, keytype, keydata = cstruct
+
+        if checkint1 != checkint2:
+            raise SSHException(
+                'OpenSSH private key file checkints do not match'
+            )
+
+        # Remove padding
+        padlen = byte_ord(keydata[len(keydata) - 1])
+        return keydata[:len(keydata) - padlen]
+
+    def _uint32_cstruct_unpack(self, data, strformat):
+        """
+        Used to read new OpenSSH private key format.
+        Unpacks a c data structure containing a mix of 32-bit uints and
+        variable length strings prefixed by 32-bit uint size field,
+        according to the specified format. Returns the unpacked vars
+        in a tuple.
+        Format strings:
+          s - denotes a string
+          i - denotes a long integer, encoded as a byte string
+          u - denotes a 32-bit unsigned integer
+          r - the remainder of the input string, returned as a string
+        """
+        arr = []
+        idx = 0
+        try:
+            for f in strformat:
+                if f == 's':
+                    # string
+                    s_size = struct.unpack('>L', data[idx:idx + 4])[0]
+                    idx += 4
+                    s = data[idx:idx + s_size]
+                    idx += s_size
+                    arr.append(s)
+                if f == 'i':
+                    # long integer
+                    s_size = struct.unpack('>L', data[idx:idx + 4])[0]
+                    idx += 4
+                    s = data[idx:idx + s_size]
+                    idx += s_size
+                    i = util.inflate_long(s, True)
+                    arr.append(i)
+                elif f == 'u':
+                    # 32-bit unsigned int
+                    u = struct.unpack('>L', data[idx:idx + 4])[0]
+                    idx += 4
+                    arr.append(u)
+                elif f == 'r':
+                    # remainder as string
+                    s = data[idx:]
+                    arr.append(s)
+                    break
+        except Exception as e:
+            raise SSHException(str(e))
+        return tuple(arr)
+
     def _write_private_key_file(self, filename, key, format, password=None):
         """
         Write an SSH2-format private key file in a form that can be read by
diff --git a/paramiko/rsakey.py b/paramiko/rsakey.py
index 442bfe1f..3ff601ec 100644
--- a/paramiko/rsakey.py
+++ b/paramiko/rsakey.py
@@ -180,12 +180,30 @@ class RSAKey(PKey):
         self._decode_key(data)
 
     def _decode_key(self, data):
-        try:
-            key = serialization.load_der_private_key(
-                data, password=None, backend=default_backend()
+        pkformat, data = data
+        if pkformat == self.PRIVATE_KEY_FORMAT_ORIGINAL:
+            try:
+                key = serialization.load_der_private_key(
+                    data, password=None, backend=default_backend()
+                )
+            except ValueError as e:
+                raise SSHException(str(e))
+        elif pkformat == self.PRIVATE_KEY_FORMAT_OPENSSH:
+            n, e, d, iqmp, q, p = self._uint32_cstruct_unpack(data, 'iiiiii')
+            public_numbers = rsa.RSAPublicNumbers(
+                e=e,
+                n=n,
             )
-        except ValueError as e:
-            raise SSHException(str(e))
-
+            key = rsa.RSAPrivateNumbers(
+                p=p,
+                q=q,
+                d=d,
+                dmp1=d % (p - 1),
+                dmq1=d % (q - 1),
+                iqmp=iqmp,
+                public_numbers=public_numbers,
+            ).private_key(default_backend())
+        else:
+            raise SSHException('unknown private key format.')
         assert isinstance(key, rsa.RSAPrivateKey)
         self.key = key
diff --git a/tests/test_dss_1k_o.key b/tests/test_dss_1k_o.key
new file mode 100644
index 00000000..2a9f8922
--- /dev/null
+++ b/tests/test_dss_1k_o.key
@@ -0,0 +1,22 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAsyq4pxL
+R5sOprPDHGpvzxAAAAEAAAAAEAAAGxAAAAB3NzaC1kc3MAAACBAL8XEx7F9xuwBNles+vW
+pNF+YcofrBhjX1r5QhpBe0eoYWLHRcroN6lxwCdGYRfgOoRjTncBiixQX/uUxAY96zDh3i
+r492s2BcJt4ihvNn/AY0I0OTuX/2IwGk9CGzafjaeZNVYxMa8lcVt0hSOTjkPQ7gVuk6bJ
+zMInvie+VWKLAAAAFQDUgYdY+rhR0SkKbC09BS/SIHcB+wAAAIB44+4zpCNcd0CGvZlowH
+99zyPX8uxQtmTLQFuR2O8O0FgVVuCdDgD0D9W8CLOp32oatpM0jyyN89EdvSWzjHzZJ+L6
+H1FtZps7uhpDFWHdva1R25vyGecLMUuXjo5t/D7oCDih+HwHoSAxoi0QvsPd8/qqHQVznN
+JKtR6thUpXEwAAAIAG4DCBjbgTTgpBw0egRkJwBSz0oTt+1IcapNU2jA6N8urMSk9YXHEQ
+HKN68BAF3YJ59q2Ujv3LOXmBqGd1T+kzwUszfMlgzq8MMu19Yfzse6AIK1Agn1Vj6F7YXL
+sXDN+T4KszX5+FJa7t/Zsp3nALWy6l0f4WKivEF5Y2QpEFcQAAAgCH6XUl1hYWB6kgCSHV
+a4C+vQHrgFNgNwEQnE074LXHXlAhxC+Dm8XTGqVPX1KRPWzadq9/+v6pqLFqiRueB86uRb
+J5WtAbUs3WwxAaC5Mi+mn42MBfL9PIwWPWCvstrAq9Nyj3EBMeX3XFLxN3RuGXIQnY/5rF
+f5hriUVxhWDQGIVbBKhkpn7Geqg6nLpn7iqQhzFmFGjPmAdrllgdVGJRLyIN6BRsaltDdy
+vxufkvGzKudvQ85QvsaoFJQ6K1d0S7907pexvxmWpcO7zchXb6i09BITWOAKIcHpVkbNQw
++8pzSdpggsAwCRbfk/Jkezz8sXVUCfmmJ23NFUw04/0ZbilCADRsUaPfafgVPeDznBnuCm
+tfXa4JSrVUvPdwoex3SKZmYsFXwsuOEQnFkhUGHfWwTbmOmxzy6dtC24KYhnWG5OGFVJXh
+3B8jQJGGs2ANfusI/Z0o15tAnQy5fqsLf9TT3RX7RG2ujIiDBsU+A1g//IXmSxxkUOQMZs
+v+cMI8KfODAXmQtB30+yAgoV03Zb/bdptv+HqPT4eeecstJUxzEGYADt1mDq3uV7fQbNmo
+80bppU52JjztrJb7hBmXsXHPRRK6spQ1FCatqvu1ggZeXZpEifNsHeqCljt87ueXsQsORY
+pvhLzjTbTKZmjLDPuB+GxUNLEKh1ZNyAqKng==
+-----END OPENSSH PRIVATE KEY-----
diff --git a/tests/test_pkey.py b/tests/test_pkey.py
index 08d38e3b..e934513d 100644
--- a/tests/test_pkey.py
+++ b/tests/test_pkey.py
@@ -25,7 +25,6 @@ import unittest
 import os
 from binascii import hexlify
 from hashlib import md5
-import base64
 
 from paramiko import RSAKey, DSSKey, ECDSAKey, Ed25519Key, Message, util
 from paramiko.py3compat import StringIO, byte_chr, b, bytes, PY2
@@ -39,6 +38,8 @@ PUB_DSS = "ssh-dss AAAAB3NzaC1kc3MAAACBAOeBpgNnfRzr/twmAQRu2XwWAp3CFtrVnug6s6fgw
 PUB_ECDSA_256 = "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJSPZm3ZWkvk/Zx8WP+fZRZ5/NBBHnGQwR6uIC6XHGPDIHuWUzIjAwA0bzqkOUffEsbLe+uQgKl5kbc/L8KA/eo="
 PUB_ECDSA_384 = "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBBbGibQLW9AAZiGN2hEQxWYYoFaWKwN3PKSaDJSMqmIn1Z9sgRUuw8Y/w502OGvXL/wFk0i2z50l3pWZjD7gfMH7gX5TUiCzwrQkS+Hn1U2S9aF5WJp0NcIzYxXw2r4M2A=="
 PUB_ECDSA_521 = "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACaOaFLZGuxa5AW16qj6VLypFbLrEWrt9AZUloCMefxO8bNLjK/O5g0rAVasar1TnyHE9qj4NwzANZASWjQNbc4MAG8vzqezFwLIn/kNyNTsXNfqEko9OgHZknlj2Z79dwTJcRAL4QLcT5aND0EHZLB2fAUDXiWIb2j4rg1mwPlBMiBXA=="
+PUB_RSA_2K_OPENSSH = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDF+Dpr54DX0WdeTDpNAMdkCWEkl3OXtNgf58qlN1gX572OLBqLf0zT4bHstUEpU3piazph/rSWcUMuBoD46tZ6jiH7H9b9Pem2eYQWaELDDkM+v9BMbEy5rMbFRLol5OtEvPFqneyEAanPOgvd8t3yyhSev9QVusakzJ8j8LGgrA8huYZ+Srnw0shEWLG70KUKCh3rG0QIvA8nfhtUOisr2Gp+F0YxMGb5gwBlQYAYE5l6u1SjZ7hNjyNosjK+wRBFgFFBYVpkZKJgWoK9w4ijFyzMZTucnZMqKOKAjIJvHfKBf2/cEfYxSq1EndqTqjYsd9T7/s2vcn1OH5a0wkER'
+PUB_DSS_1K_OPENSSH = 'ssh-dss AAAAB3NzaC1kc3MAAACBAL8XEx7F9xuwBNles+vWpNF+YcofrBhjX1r5QhpBe0eoYWLHRcroN6lxwCdGYRfgOoRjTncBiixQX/uUxAY96zDh3ir492s2BcJt4ihvNn/AY0I0OTuX/2IwGk9CGzafjaeZNVYxMa8lcVt0hSOTjkPQ7gVuk6bJzMInvie+VWKLAAAAFQDUgYdY+rhR0SkKbC09BS/SIHcB+wAAAIB44+4zpCNcd0CGvZlowH99zyPX8uxQtmTLQFuR2O8O0FgVVuCdDgD0D9W8CLOp32oatpM0jyyN89EdvSWzjHzZJ+L6H1FtZps7uhpDFWHdva1R25vyGecLMUuXjo5t/D7oCDih+HwHoSAxoi0QvsPd8/qqHQVznNJKtR6thUpXEwAAAIAG4DCBjbgTTgpBw0egRkJwBSz0oTt+1IcapNU2jA6N8urMSk9YXHEQHKN68BAF3YJ59q2Ujv3LOXmBqGd1T+kzwUszfMlgzq8MMu19Yfzse6AIK1Agn1Vj6F7YXLsXDN+T4KszX5+FJa7t/Zsp3nALWy6l0f4WKivEF5Y2QpEFcQ=='
 
 FINGER_RSA = "1024 60:73:38:44:cb:51:86:65:7f:de:da:a2:2b:5a:57:d5"
 FINGER_DSS = "1024 44:78:f0:b9:a2:3c:c5:18:20:09:ff:75:5b:c1:d2:6c"
@@ -46,6 +47,8 @@ FINGER_ECDSA_256 = "256 25:19:eb:55:e6:a1:47:ff:4f:38:d2:75:6f:a5:d5:60"
 FINGER_ECDSA_384 = "384 c1:8d:a0:59:09:47:41:8e:a8:a6:07:01:29:23:b4:65"
 FINGER_ECDSA_521 = "521 44:58:22:52:12:33:16:0e:ce:0e:be:2c:7c:7e:cc:1e"
 SIGNED_RSA = "20:d7:8a:31:21:cb:f7:92:12:f2:a4:89:37:f5:78:af:e6:16:b6:25:b9:97:3d:a2:cd:5f:ca:20:21:73:4c:ad:34:73:8f:20:77:28:e2:94:15:08:d8:91:40:7a:85:83:bf:18:37:95:dc:54:1a:9b:88:29:6c:73:ca:38:b4:04:f1:56:b9:f2:42:9d:52:1b:29:29:b4:4f:fd:c9:2d:af:47:d2:40:76:30:f3:63:45:0c:d9:1d:43:86:0f:1c:70:e2:93:12:34:f3:ac:c5:0a:2f:14:50:66:59:f1:88:ee:c1:4a:e9:d1:9c:4e:46:f0:0e:47:6f:38:74:f1:44:a8"
+FINGER_RSA_2K_OPENSSH = '2048 68:d1:72:01:bf:c0:0c:66:97:78:df:ce:75:74:46:d6'
+FINGER_DSS_1K_OPENSSH = '1024 cf:1d:eb:d7:61:d3:12:94:c6:c0:c6:54:35:35:b0:82'
 
 RSA_PRIVATE_OUT = """\
 -----BEGIN RSA PRIVATE KEY-----
@@ -438,6 +441,26 @@ class KeyTest(unittest.TestCase):
         pub = ECDSAKey(data=key.asbytes())
         self.assertTrue(pub.verify_ssh_sig(b"ice weasels", msg))
 
+    def test_22_load_RSA_key_new_format(self):
+        key = RSAKey.from_private_key_file(
+            _support('test_rsa_2k_o.key'), b'television')
+        self.assertEqual('ssh-rsa', key.get_name())
+        self.assertEqual(PUB_RSA_2K_OPENSSH.split()[1], key.get_base64())
+        self.assertEqual(2048, key.get_bits())
+        exp_rsa = b(FINGER_RSA_2K_OPENSSH.split()[1].replace(':', ''))
+        my_rsa = hexlify(key.get_fingerprint())
+        self.assertEqual(exp_rsa, my_rsa)
+
+    def test_23_load_DSS_key_new_format(self):
+        key = DSSKey.from_private_key_file(
+            _support('test_dss_1k_o.key'), b'television')
+        self.assertEqual('ssh-dss', key.get_name())
+        self.assertEqual(PUB_DSS_1K_OPENSSH.split()[1], key.get_base64())
+        self.assertEqual(1024, key.get_bits())
+        exp_rsa = b(FINGER_DSS_1K_OPENSSH.split()[1].replace(':', ''))
+        my_rsa = hexlify(key.get_fingerprint())
+        self.assertEqual(exp_rsa, my_rsa)
+
     def test_salt_size(self):
         # Read an existing encrypted private key
         file_ = _support("test_rsa_password.key")
diff --git a/tests/test_rsa_2k_o.key b/tests/test_rsa_2k_o.key
new file mode 100644
index 00000000..afc15f1c
--- /dev/null
+++ b/tests/test_rsa_2k_o.key
@@ -0,0 +1,28 @@
+-----BEGIN OPENSSH PRIVATE KEY-----
+ b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABD0R3hOFS
+ FMb2SJeo5h8QPNAAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQDF+Dpr54DX
+ 0WdeTDpNAMdkCWEkl3OXtNgf58qlN1gX572OLBqLf0zT4bHstUEpU3piazph/rSWcUMuBo
+ D46tZ6jiH7H9b9Pem2eYQWaELDDkM+v9BMbEy5rMbFRLol5OtEvPFqneyEAanPOgvd8t3y
+ yhSev9QVusakzJ8j8LGgrA8huYZ+Srnw0shEWLG70KUKCh3rG0QIvA8nfhtUOisr2Gp+F0
+ YxMGb5gwBlQYAYE5l6u1SjZ7hNjyNosjK+wRBFgFFBYVpkZKJgWoK9w4ijFyzMZTucnZMq
+ KOKAjIJvHfKBf2/cEfYxSq1EndqTqjYsd9T7/s2vcn1OH5a0wkERAAAD0JnzCJYfDeiUQ6
+ 9LOAb6/NnhKvFjCdBYal60MfLcLBHvzHLJvTneQ4f1Vknq8xEVmRba7SDSfwaEybP/1FsP
+ SGH6FNKA5gKllemgmcaUVr3wtNPtjX4WgsyHcwCRgHmOiyNrUj0OZR5wbZabHIIyirl4wa
+ LBz8Jb3GalKEagtyWsBKDCKHCFNzh8xmsT1SWhnC7baRyC8e3krQm9hGbNhpj6Q5AtN3ql
+ wBVamUp0eKxkt70mKBKI4v3DR8KqrEndeK6d0cegVEkE67fqa99a5J3uSDC8mglKrHiKEs
+ dU1dh/bOF/H3aFpINlRwvlZ95Opby7rG0BHgbZONq0+VUnABVzNTM5Xd5UKjjCF28CrQBf
+ XS6WeHeUx2zHtOmL1xdePk+Bii+SSUl3pLa4SDwX4nV95cSPx8vMm8dJEruxad6+MPoSuy
+ Oyho89jqUTSgC/RPejuTgrnB3WbzE5SJb+V3zMata0J1wxbNfYKG9U+VucUZhP4+jzfNqH
+ B/v8JqtuxnqR8NjPsK2+8wJxebL2KVNjKOm//6P3KSDsavpscGpVWOM06zUlwWCB26W3pP
+ X/+xO9aR5wiBteFKoJG1waziIjqhOJSmvq+I/texUKEUd/eEFNt10Ubc0zy0sRYVN8rIRJ
+ masQzCYuUylDzCa4ar1s4qngBZzWL2PRkPuXuhoHuT0J5no174GR6+6EAYZZhnq0tkYrhZ
+ Ar0tQ4CDlI235a3MPHzvABuwYuWys1tBuLAb+6Gc6CmCiQ+mhojfQUBYG5T65iRFA5UQsH
+ O1RLEC3yasxGcBI6d0J/fwOP/YLktNu3AeUumr0N9Xgf02DlBNwd+4GOI0LcQvl/3J8ppo
+ bamTppKPEZ2d32VNEO+Z6Zx5DlIVm5gDeMvIvdwap445VnhL3ZZH2NCkAcXM9+0WH+Quas
+ JCAMgPYiP9FzF+8Onmj2OmhgIVj/9eanhS3/GLrRC4xCvER2V7PwgB0I5qY110BPEttDyo
+ IvYE51kvtdW447SK7HZywJnkyw2RNm+29dvWJJwSQckUHuZkXEtmEPk0ePL3yf2NH5XYJc
+ pXX6Zac0KemCPIHr8l7GogE4Rb2BBTqddkegb9piz6QTAPcQnn+GuMFG06IBhUrgcMEQ8x
+ UOXYUUrT5HvSxWUcgaYH1nfC3bTWmDaodw8/HQKyF6c44rujO2s2NLFOCAyQMUNdhh3lfD
+ yHYLO7xYkP6xzzkpk+2lwBoeYdQdAwlKN/XqC8ZhBfwTdem/1hh1BpQJFbbFftWxU8gxxi
+ iuI+vmlsuIsxKoGCq8YXuophx62lo=
+ -----END OPENSSH PRIVATE KEY-----
-- 
2.25.1

