def _read_private_key_old_format(cls, headers, data, password): if 'proc-type' not in headers: # unencryped: done return data # encrypted keyfile: will need a password proc_type = headers['proc-type'] if proc_type != '4,ENCRYPTED': raise SSHException( 'Unknown private key structure "{}"'.format(proc_type)) try: encryption_type, saltstr = headers['dek-info'].split(',') except: raise SSHException("Can't parse DEK-info in private key file") if encryption_type not in cls._CIPHER_TABLE: raise SSHException( 'Unknown private key cipher "{}"'.format(encryption_type)) # 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') cipher = cls._CIPHER_TABLE[encryption_type]['cipher'] keysize = cls._CIPHER_TABLE[encryption_type]['keysize'] mode = cls._CIPHER_TABLE[encryption_type]['mode'] salt = unhexlify(b(saltstr)) key = util.generate_key_bytes(md5, salt, password, keysize) decryptor = Cipher(cipher(key), mode(salt), backend=default_backend()).decryptor() return decryptor.update(data) + decryptor.finalize()
def test_ssh_client_connect_immediately_throws_on_fatal_execption(self): # Verify that fatal exceptions are immediately propagated and ensure # we don't try to retry on them from paramiko.ssh_exception import SSHException from paramiko.ssh_exception import PasswordRequiredException mock_ssh_client = Mock() mock_ssh_client.connect = Mock() mock_ssh_client.connect.side_effect = IOError('bam') mock_exceptions = [ SSHException('Invalid or unsupported key type'), PasswordRequiredException('private key file is encrypted'), SSHException('OpenSSH private key file checkints do not match') ] for mock_exception in mock_exceptions: mock_ssh_client.connect = Mock(side_effect=mock_exception) assertRaisesRegex(self, mock_exception.__class__, str(mock_exception), self.driver._ssh_client_connect, ssh_client=mock_ssh_client, wait_period=0.1, timeout=0.2)
def _read_private_key(self, tag, f, password=None): lines = f.readlines() start = 0 beginning_of_key = "-----BEGIN " + tag + " PRIVATE KEY-----" while start < len(lines) and lines[start].strip() != beginning_of_key: start += 1 if start >= len(lines): raise SSHException("not a valid " + tag + " private key file") # parse any headers first headers = {} start += 1 while start < len(lines): l = lines[start].split(": ") if len(l) == 1: break headers[l[0].lower()] = l[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]))) except base64.binascii.Error as e: raise SSHException("base64 decoding error: " + str(e)) if "proc-type" not in headers: # unencryped: done return data # encrypted keyfile: will need a password proc_type = headers["proc-type"] if proc_type != "4,ENCRYPTED": raise SSHException( 'Unknown private key structure "{}"'.format(proc_type) ) try: encryption_type, saltstr = headers["dek-info"].split(",") except: raise SSHException("Can't parse DEK-info in private key file") if encryption_type not in self._CIPHER_TABLE: raise SSHException( 'Unknown private key cipher "{}"'.format(encryption_type) ) # 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") cipher = self._CIPHER_TABLE[encryption_type]["cipher"] keysize = self._CIPHER_TABLE[encryption_type]["keysize"] mode = self._CIPHER_TABLE[encryption_type]["mode"] salt = unhexlify(b(saltstr)) key = util.generate_key_bytes(md5, salt, password, keysize) decryptor = Cipher( cipher(key), mode(salt), backend=default_backend() ).decryptor() return decryptor.update(data) + decryptor.finalize()
def _read_private_key(self, tag, f, password=None): lines = f.readlines() start = 0 beginning_of_key = '-----BEGIN ' + tag + ' PRIVATE KEY-----' while start < len(lines) and lines[start].strip() != beginning_of_key: start += 1 if start >= len(lines): raise SSHException('not a valid ' + tag + ' private key file') # parse any headers first headers = {} start += 1 while start < len(lines): l = lines[start].split(': ') if len(l) == 1: break headers[l[0].lower()] = l[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]))) except base64.binascii.Error as e: raise SSHException('base64 decoding error: ' + str(e)) if 'proc-type' not in headers: # unencryped: done return data # encrypted keyfile: will need a password if headers['proc-type'] != '4,ENCRYPTED': raise SSHException('Unknown private key structure "%s"' % headers['proc-type']) try: encryption_type, saltstr = headers['dek-info'].split(',') except: raise SSHException("Can't parse DEK-info in private key file") if encryption_type not in self._CIPHER_TABLE: raise SSHException('Unknown private key cipher "%s"' % encryption_type) # 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') cipher = self._CIPHER_TABLE[encryption_type]['cipher'] keysize = self._CIPHER_TABLE[encryption_type]['keysize'] mode = self._CIPHER_TABLE[encryption_type]['mode'] salt = unhexlify(b(saltstr)) key = util.generate_key_bytes(md5, salt, password, keysize) decryptor = Cipher(cipher(key), mode(salt), backend=default_backend()).decryptor() return decryptor.update(data) + decryptor.finalize()
def _read_private_key_old_format(self, lines, end, password): start = 0 # parse any headers first headers = {} start += 1 while start < len(lines): l = lines[start].split(': ') if len(l) == 1: break headers[l[0].lower()] = l[1].strip() start += 1 # if we trudged to the end of the file, just try to cope. try: data = decodebytes(b(''.join(lines[start:end]))) except base64.binascii.Error as e: raise SSHException('base64 decoding error: ' + str(e)) if 'proc-type' not in headers: # unencryped: done return data # encrypted keyfile: will need a password proc_type = headers['proc-type'] if proc_type != '4,ENCRYPTED': raise SSHException( 'Unknown private key structure "{}"'.format(proc_type)) try: encryption_type, saltstr = headers['dek-info'].split(',') except: raise SSHException("Can't parse DEK-info in private key file") if encryption_type not in self._CIPHER_TABLE: raise SSHException( 'Unknown private key cipher "{}"'.format(encryption_type)) # 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') cipher = self._CIPHER_TABLE[encryption_type]['cipher'] keysize = self._CIPHER_TABLE[encryption_type]['keysize'] mode = self._CIPHER_TABLE[encryption_type]['mode'] salt = unhexlify(b(saltstr)) key = util.generate_key_bytes(md5, salt, password, keysize) decryptor = Cipher(cipher(key), mode(salt), backend=default_backend()).decryptor() return decryptor.update(data) + decryptor.finalize()
class PKey(object): """ Base class for public keys. """ # known encryption types for private key files: _CIPHER_TABLE = { 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC } } def __init__(self, msg=None, data=None): """ Create a new instance of this public key type. If C{msg} is given, the key's public part(s) will be filled in from the message. If C{data} is given, the key's public part(s) will be filled in from the string. @param msg: an optional SSH L{Message} containing a public key of this type. @type msg: L{Message} @param data: an optional string containing a public key of this type @type data: str @raise SSHException: if a key cannot be created from the C{data} or C{msg} given, or no key was passed in. """ pass def __str__(self): """ Return a string of an SSH L{Message} made up of the public part(s) of this key. This string is suitable for passing to L{__init__} to re-create the key object later. @return: string representation of an SSH key message. @rtype: str """ return '' def __cmp__(self, other): """ Compare this key to another. Returns 0 if this key is equivalent to the given key, or non-0 if they are different. Only the public parts of the key are compared, so a public key will compare equal to its corresponding private key. @param other: key to compare to. @type other: L{PKey} @return: 0 if the two keys are equivalent, non-0 otherwise. @rtype: int """ hs = hash(self) ho = hash(other) if hs != ho: return cmp(hs, ho) return cmp(str(self), str(other)) def get_name(self): """ Return the name of this private key implementation. @return: name of this private key type, in SSH terminology (for example, C{"ssh-rsa"}). @rtype: str """ return '' def get_bits(self): """ Return the number of significant bits in this key. This is useful for judging the relative security of a key. @return: bits in the key. @rtype: int """ return 0 def can_sign(self): """ Return C{True} if this key has the private part necessary for signing data. @return: C{True} if this is a private key. @rtype: bool """ return False def get_fingerprint(self): """ Return an MD5 fingerprint of the public part of this key. Nothing secret is revealed. @return: a 16-byte string (binary) of the MD5 fingerprint, in SSH format. @rtype: str """ return MD5.new(str(self)).digest() def get_base64(self): """ Return a base64 string containing the public part of this key. Nothing secret is revealed. This format is compatible with that used to store public key files or recognized host keys. @return: a base64 string containing the public part of the key. @rtype: str """ return base64.encodestring(str(self)).replace('\n', '') def sign_ssh_data(self, randpool, data): """ Sign a blob of data with this private key, and return a L{Message} representing an SSH signature message. @param randpool: a secure random number generator. @type randpool: L{Crypto.Util.randpool.RandomPool} @param data: the data to sign. @type data: str @return: an SSH signature message. @rtype: L{Message} """ return '' def verify_ssh_sig(self, data, msg): """ Given a blob of data, and an SSH message representing a signature of that data, verify that it was signed with this key. @param data: the data that was signed. @type data: str @param msg: an SSH signature message @type msg: L{Message} @return: C{True} if the signature verifies correctly; C{False} otherwise. @rtype: boolean """ return False def from_private_key_file(cls, filename, password=None): """ Create a key object by reading a private key file. If the private key is encrypted and C{password} is not C{None}, the given password will be used to decrypt the key (otherwise L{PasswordRequiredException} is thrown). Through the magic of python, this factory method will exist in all subclasses of PKey (such as L{RSAKey} or L{DSSKey}), but is useless on the abstract PKey class. @param filename: name of the file to read @type filename: str @param password: an optional password to use to decrypt the key file, if it's encrypted @type password: str @return: a new key object based on the given private key @rtype: L{PKey} @raise IOError: if there was an error reading the file @raise PasswordRequiredException: if the private key file is encrypted, and C{password} is C{None} @raise SSHException: if the key file is invalid """ key = cls(filename=filename, password=password) return key from_private_key_file = classmethod(from_private_key_file) def from_private_key(cls, file_obj, password=None): """ Create a key object by reading a private key from a file (or file-like) object. If the private key is encrypted and C{password} is not C{None}, the given password will be used to decrypt the key (otherwise L{PasswordRequiredException} is thrown). @param file_obj: the file to read from @type file_obj: file @param password: an optional password to use to decrypt the key, if it's encrypted @type password: str @return: a new key object based on the given private key @rtype: L{PKey} @raise IOError: if there was an error reading the key @raise PasswordRequiredException: if the private key file is encrypted, and C{password} is C{None} @raise SSHException: if the key file is invalid """ key = cls(file_obj=file_obj, password=password) return key from_private_key = classmethod(from_private_key) def write_private_key_file(self, filename, password=None): """ Write private key contents into a file. If the password is not C{None}, the key is encrypted before writing. @param filename: name of the file to write @type filename: str @param password: an optional password to use to encrypt the key file @type password: str @raise IOError: if there was an error writing the file @raise SSHException: if the key is invalid """ raise Exception('Not implemented in PKey') def write_private_key(self, file_obj, password=None): """ Write private key contents into a file (or file-like) object. If the password is not C{None}, the key is encrypted before writing. @param file_obj: the file object to write into @type file_obj: file @param password: an optional password to use to encrypt the key @type password: str @raise IOError: if there was an error writing to the file @raise SSHException: if the key is invalid """ raise Exception('Not implemented in PKey') def _read_private_key_file(self, tag, filename, password=None): """ Read an SSH2-format private key file, looking for a string of the type C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we find, and return it as a string. If the private key is encrypted and C{password} is not C{None}, the given password will be used to decrypt the key (otherwise L{PasswordRequiredException} is thrown). @param tag: C{"RSA"} or C{"DSA"}, the tag used to mark the data block. @type tag: str @param filename: name of the file to read. @type filename: str @param password: an optional password to use to decrypt the key file, if it's encrypted. @type password: str @return: data blob that makes up the private key. @rtype: str @raise IOError: if there was an error reading the file. @raise PasswordRequiredException: if the private key file is encrypted, and C{password} is C{None}. @raise SSHException: if the key file is invalid. """ f = open(filename, 'r') data = self._read_private_key(tag, f, password) f.close() return data def _read_private_key(self, tag, f, password=None): lines = f.readlines() start = 0 while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'): start += 1 if start >= len(lines): raise SSHException('not a valid ' + tag + ' private key file') # parse any headers first headers = {} start += 1 while start < len(lines): l = lines[start].split(': ') if len(l) == 1: break headers[l[0].lower()] = l[1].strip() start += 1 # find end end = start while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)): end += 1 # if we trudged to the end of the file, just try to cope. try: data = base64.decodestring(''.join(lines[start:end])) except base64.binascii.Error, e: raise SSHException('base64 decoding error: ' + str(e)) if 'proc-type' not in headers: # unencryped: done return data # encrypted keyfile: will need a password if headers['proc-type'] != '4,ENCRYPTED': raise SSHException('Unknown private key structure "%s"' % headers['proc-type']) try: encryption_type, saltstr = headers['dek-info'].split(',') except: raise SSHException('Can\'t parse DEK-info in private key file') if encryption_type not in self._CIPHER_TABLE: raise SSHException('Unknown private key cipher "%s"' % encryption_type) # 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') cipher = self._CIPHER_TABLE[encryption_type]['cipher'] keysize = self._CIPHER_TABLE[encryption_type]['keysize'] mode = self._CIPHER_TABLE[encryption_type]['mode'] salt = unhexlify(saltstr) key = util.generate_key_bytes(MD5, salt, password, keysize) return cipher.new(key, mode, salt).decrypt(data)
def _parse_signing_key_data(self, data, password): from paramiko.transport import Transport # We may eventually want this to be usable for other key types, as # OpenSSH moves to it, but for now this is just for Ed25519 keys. # This format is described here: # https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key # The description isn't totally complete, and I had to refer to the # source for a full implementation. message = Message(data) if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC: raise SSHException("Invalid key") ciphername = message.get_text() kdfname = message.get_text() kdfoptions = message.get_binary() num_keys = message.get_int() if kdfname == "none": # kdfname of "none" must have an empty kdfoptions, the ciphername # must be "none" if kdfoptions or ciphername != "none": raise SSHException("Invalid key") elif kdfname == "bcrypt": if not password: raise PasswordRequiredException( "Private key file is encrypted" ) kdf = Message(kdfoptions) bcrypt_salt = kdf.get_binary() bcrypt_rounds = kdf.get_int() else: raise SSHException("Invalid key") if ciphername != "none" and ciphername not in Transport._cipher_info: raise SSHException("Invalid key") public_keys = [] for _ in range(num_keys): pubkey = Message(message.get_binary()) if pubkey.get_text() != "ssh-ed25519": raise SSHException("Invalid key") public_keys.append(pubkey.get_binary()) private_ciphertext = message.get_binary() if ciphername == "none": private_data = private_ciphertext else: cipher = Transport._cipher_info[ciphername] key = bcrypt.kdf( password=b(password), salt=bcrypt_salt, desired_key_bytes=cipher["key-size"] + cipher["block-size"], rounds=bcrypt_rounds, # We can't control how many rounds are on disk, so no sense # warning about it. ignore_few_rounds=True, ) decryptor = Cipher( cipher["class"](key[: cipher["key-size"]]), cipher["mode"](key[cipher["key-size"] :]), backend=default_backend(), ).decryptor() private_data = ( decryptor.update(private_ciphertext) + decryptor.finalize() ) message = Message(unpad(private_data)) if message.get_int() != message.get_int(): raise SSHException("Invalid key") signing_keys = [] for i in range(num_keys): if message.get_text() != "ssh-ed25519": raise SSHException("Invalid key") # A copy of the public key, again, ignore. public = message.get_binary() key_data = message.get_binary() # The second half of the key data is yet another copy of the public # key... signing_key = nacl.signing.SigningKey(key_data[:32]) # Verify that all the public keys are the same... assert ( signing_key.verify_key.encode() == public == public_keys[i] == key_data[32:] ) signing_keys.append(signing_key) # Comment, ignore. message.get_binary() if len(signing_keys) != 1: raise SSHException("Invalid key") return signing_keys[0]
class PKey(object): """ Base class for public keys. """ # known encryption types for private key files: _CIPHER_TABLE = { 'AES-128-CBC': { 'cipher': AES, 'keysize': 16, 'blocksize': 16, 'mode': AES.MODE_CBC }, 'DES-EDE3-CBC': { 'cipher': DES3, 'keysize': 24, 'blocksize': 8, 'mode': DES3.MODE_CBC }, } def __init__(self, msg=None, data=None): """ Create a new instance of this public key type. If ``msg`` is given, the key's public part(s) will be filled in from the message. If ``data`` is given, the key's public part(s) will be filled in from the string. :param .Message msg: an optional SSH `.Message` containing a public key of this type. :param str data: an optional string containing a public key of this type :raises SSHException: if a key cannot be created from the ``data`` or ``msg`` given, or no key was passed in. """ pass def __str__(self): """ Return a string of an SSH `.Message` made up of the public part(s) of this key. This string is suitable for passing to `__init__` to re-create the key object later. """ return '' def __cmp__(self, other): """ Compare this key to another. Returns 0 if this key is equivalent to the given key, or non-0 if they are different. Only the public parts of the key are compared, so a public key will compare equal to its corresponding private key. :param .Pkey other: key to compare to. """ hs = hash(self) ho = hash(other) if hs != ho: return cmp(hs, ho) return cmp(str(self), str(other)) def get_name(self): """ Return the name of this private key implementation. :return: name of this private key type, in SSH terminology, as a `str` (for example, ``"ssh-rsa"``). """ return '' def get_bits(self): """ Return the number of significant bits in this key. This is useful for judging the relative security of a key. :return: bits in the key (as an `int`) """ return 0 def can_sign(self): """ Return ``True`` if this key has the private part necessary for signing data. """ return False def get_fingerprint(self): """ Return an MD5 fingerprint of the public part of this key. Nothing secret is revealed. :return: a 16-byte `string <str>` (binary) of the MD5 fingerprint, in SSH format. """ return MD5.new(str(self)).digest() def get_base64(self): """ Return a base64 string containing the public part of this key. Nothing secret is revealed. This format is compatible with that used to store public key files or recognized host keys. :return: a base64 `string <str>` containing the public part of the key. """ return base64.encodestring(str(self)).replace('\n', '') def sign_ssh_data(self, rng, data): """ Sign a blob of data with this private key, and return a `.Message` representing an SSH signature message. :param .Crypto.Util.rng.RandomPool rng: a secure random number generator. :param str data: the data to sign. :return: an SSH signature `message <.Message>`. """ return '' def verify_ssh_sig(self, data, msg): """ Given a blob of data, and an SSH message representing a signature of that data, verify that it was signed with this key. :param str data: the data that was signed. :param .Message msg: an SSH signature message :return: ``True`` if the signature verifies correctly; ``False`` otherwise. """ return False def from_private_key_file(cls, filename, password=None): """ Create a key object by reading a private key file. If the private key is encrypted and ``password`` is not ``None``, the given password will be used to decrypt the key (otherwise `.PasswordRequiredException` is thrown). Through the magic of Python, this factory method will exist in all subclasses of PKey (such as `.RSAKey` or `.DSSKey`), but is useless on the abstract PKey class. :param str filename: name of the file to read :param str password: an optional password to use to decrypt the key file, if it's encrypted :return: a new `.PKey` based on the given private key :raises IOError: if there was an error reading the file :raises PasswordRequiredException: if the private key file is encrypted, and ``password`` is ``None`` :raises SSHException: if the key file is invalid """ key = cls(filename=filename, password=password) return key from_private_key_file = classmethod(from_private_key_file) def from_private_key(cls, file_obj, password=None): """ Create a key object by reading a private key from a file (or file-like) object. If the private key is encrypted and ``password`` is not ``None``, the given password will be used to decrypt the key (otherwise `.PasswordRequiredException` is thrown). :param file file_obj: the file to read from :param str password: an optional password to use to decrypt the key, if it's encrypted :return: a new `.PKey` based on the given private key :raises IOError: if there was an error reading the key :raises PasswordRequiredException: if the private key file is encrypted, and ``password`` is ``None`` :raises SSHException: if the key file is invalid """ key = cls(file_obj=file_obj, password=password) return key from_private_key = classmethod(from_private_key) def write_private_key_file(self, filename, password=None): """ Write private key contents into a file. If the password is not ``None``, the key is encrypted before writing. :param str filename: name of the file to write :param str password: an optional password to use to encrypt the key file :raises IOError: if there was an error writing the file :raises SSHException: if the key is invalid """ raise Exception('Not implemented in PKey') def write_private_key(self, file_obj, password=None): """ Write private key contents into a file (or file-like) object. If the password is not ``None``, the key is encrypted before writing. :param file file_obj: the file object to write into :param str password: an optional password to use to encrypt the key :raises IOError: if there was an error writing to the file :raises SSHException: if the key is invalid """ raise Exception('Not implemented in PKey') def _read_private_key_file(self, tag, filename, password=None): """ Read an SSH2-format private key file, looking for a string of the type ``"BEGIN xxx PRIVATE KEY"`` for some ``xxx``, base64-decode the text we find, and return it as a string. If the private key is encrypted and ``password`` is not ``None``, the given password will be used to decrypt the key (otherwise `.PasswordRequiredException` is thrown). :param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the data block. :param str filename: name of the file to read. :param str password: an optional password to use to decrypt the key file, if it's encrypted. :return: data blob (`str`) that makes up the private key. :raises IOError: if there was an error reading the file. :raises PasswordRequiredException: if the private key file is encrypted, and ``password`` is ``None``. :raises SSHException: if the key file is invalid. """ f = open(filename, 'r') data = self._read_private_key(tag, f, password) f.close() return data def _read_private_key(self, tag, f, password=None): lines = f.readlines() start = 0 while (start < len(lines)) and (lines[start].strip() != '-----BEGIN ' + tag + ' PRIVATE KEY-----'): start += 1 if start >= len(lines): raise SSHException('not a valid ' + tag + ' private key file') # parse any headers first headers = {} start += 1 while start < len(lines): l = lines[start].split(': ') if len(l) == 1: break headers[l[0].lower()] = l[1].strip() start += 1 # find end end = start while (lines[end].strip() != '-----END ' + tag + ' PRIVATE KEY-----') and (end < len(lines)): end += 1 # if we trudged to the end of the file, just try to cope. try: data = base64.decodestring(''.join(lines[start:end])) except base64.binascii.Error, e: raise SSHException('base64 decoding error: ' + str(e)) if 'proc-type' not in headers: # unencryped: done return data # encrypted keyfile: will need a password if headers['proc-type'] != '4,ENCRYPTED': raise SSHException('Unknown private key structure "%s"' % headers['proc-type']) try: encryption_type, saltstr = headers['dek-info'].split(',') except: raise SSHException('Can\'t parse DEK-info in private key file') if encryption_type not in self._CIPHER_TABLE: raise SSHException('Unknown private key cipher "%s"' % encryption_type) # 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') cipher = self._CIPHER_TABLE[encryption_type]['cipher'] keysize = self._CIPHER_TABLE[encryption_type]['keysize'] mode = self._CIPHER_TABLE[encryption_type]['mode'] salt = unhexlify(saltstr) key = util.generate_key_bytes(MD5, salt, password, keysize) return cipher.new(key, mode, salt).decrypt(data)
def _read_private_key_openssh(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: {}".format(e)) # read data struct auth_magic = data[:15] if auth_magic != OPENSSH_AUTH_MAGIC: 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, # We can't control how many rounds are on disk, so no sense # warning about it. ignore_few_rounds=True, ) 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") return _unpad_openssh(keydata)
def _read_private_key_new_format(data, 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 https://coolaj86.com/articles/the-openssh-private-key-format/ """ message = Message(data) OPENSSH_AUTH_MAGIC = b"openssh-key-v1\x00" if message.get_bytes(len(OPENSSH_AUTH_MAGIC)) != OPENSSH_AUTH_MAGIC: raise SSHException("unexpected OpenSSH key header encountered") cipher = message.get_text() kdfname = message.get_text() kdfoptions = message.get_binary() num_keys = message.get_int() if num_keys > 1: raise SSHException( "unsupported: private keyfile has multiple keys") public_data = message.get_binary() privkey_blob = message.get_binary() pub_keytype = Message(public_data).get_text() if kdfname == "none": if kdfoptions or cipher != "none": raise SSHException("Invalid key options for kdf 'none'") private_data = privkey_blob elif kdfname == "bcrypt": if not password: raise PasswordRequiredException( "Private key file is encrypted") if cipher == 'aes256-cbc': mode = modes.CBC elif cipher == 'aes256-ctr': mode = modes.CTR else: raise SSHException( "unknown cipher '%s' used in private key file" % cipher) kdf = Message(kdfoptions) salt = kdf.get_binary() rounds = kdf.get_int() # run bcrypt kdf to derive key and iv/nonce (desired_key_bytes = 32 + 16 bytes) key_iv = bcrypt.kdf(b(password), salt, 48, rounds, ignore_few_rounds=True) key = key_iv[:32] iv = key_iv[32:] # decrypt private key blob decryptor = Cipher(algorithms.AES(key), mode(iv), default_backend()).decryptor() private_data = decryptor.update( privkey_blob) + decryptor.finalize() else: raise SSHException( "unknown cipher or kdf used in private key file") # Unpack private key and verify checkints priv_msg = Message(private_data) checkint1 = priv_msg.get_int() checkint2 = priv_msg.get_int() if checkint1 != checkint2: raise SSHException( 'OpenSSH private key file checkints do not match') keytype = priv_msg.get_text() if pub_keytype != keytype: raise SSHException( "Inconsistent key types for public and private parts") keydata = priv_msg.get_remainder() return keytype, _unpad(keydata)