def _read_private_key_new_format(self, typepfx, 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 https://coolaj86.com/articles/the-openssh-private-key-format/ """ try: data = decodebytes(b(''.join(lines))) except base64.binascii.Error as e: raise SSHException('base64 decoding error: ' + str(e)) 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() keytype = Message(public_data).get_text() if not keytype.startswith(typepfx): raise SSHException("Invalid key type name (public part)") 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 not keytype.startswith(typepfx): raise SSHException("Invalid key type name (private part)") keydata = priv_msg.get_remainder() return unpad(keydata)