aboutsummaryrefslogtreecommitdiff
path: root/paramiko/pkey.py
diff options
context:
space:
mode:
Diffstat (limited to 'paramiko/pkey.py')
-rw-r--r--paramiko/pkey.py265
1 files changed, 117 insertions, 148 deletions
diff --git a/paramiko/pkey.py b/paramiko/pkey.py
index b1199df..373563f 100644
--- a/paramiko/pkey.py
+++ b/paramiko/pkey.py
@@ -23,13 +23,13 @@ Common API for all public keys.
import base64
from binascii import hexlify, unhexlify
import os
+from hashlib import md5
-from Crypto.Hash import MD5
from Crypto.Cipher import DES3, AES
-from paramiko.common import *
from paramiko import util
-from paramiko.message import Message
+from paramiko.common import o600, zero_byte
+from paramiko.py3compat import u, encodebytes, decodebytes, b
from paramiko.ssh_exception import SSHException, PasswordRequiredException
@@ -40,40 +40,39 @@ class PKey (object):
# 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 },
+ '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 C{msg} is given,
+ 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
- C{data} is given, the key's public part(s) will be filled in from
+ ``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
+ :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
- @raise SSHException: if a key cannot be created from the C{data} or
- C{msg} given, or no key was passed in.
+ :raises SSHException:
+ if a key cannot be created from the ``data`` or ``msg`` given, or
+ no key was passed in.
"""
pass
- def __str__(self):
+ def asbytes(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
+ 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: string representation of an SSH key message.
- @rtype: str
"""
- return ''
+ return bytes()
+ def __str__(self):
+ return self.asbytes()
+
+ # noinspection PyUnresolvedReferences
def __cmp__(self, other):
"""
Compare this key to another. Returns 0 if this key is equivalent to
@@ -81,24 +80,24 @@ class PKey (object):
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
+ :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))
+ return cmp(self.asbytes(), other.asbytes())
+
+ def __eq__(self, other):
+ return hash(self) == hash(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:
+ name of this private key type, in SSH terminology, as a `str` (for
+ example, ``"ssh-rsa"``).
"""
return ''
@@ -107,18 +106,14 @@ class PKey (object):
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: bits in the key (as an `int`)
"""
return 0
def can_sign(self):
"""
- Return C{True} if this key has the private part necessary for signing
+ Return ``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
@@ -127,11 +122,11 @@ class PKey (object):
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
+ :return:
+ a 16-byte `string <str>` (binary) of the MD5 fingerprint, in SSH
format.
- @rtype: str
"""
- return MD5.new(str(self)).digest()
+ return md5(self.asbytes()).digest()
def get_base64(self):
"""
@@ -139,61 +134,50 @@ class PKey (object):
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: a base64 `string <str>` containing the public part of the key.
"""
- return base64.encodestring(str(self)).replace('\n', '')
+ return u(encodebytes(self.asbytes())).replace('\n', '')
- def sign_ssh_data(self, rng, data):
+ def sign_ssh_data(self, data):
"""
- Sign a blob of data with this private key, and return a L{Message}
+ Sign a blob of data with this private key, and return a `.Message`
representing an SSH signature message.
- @param rng: a secure random number generator.
- @type rng: L{Crypto.Util.rng.RandomPool}
- @param data: the data to sign.
- @type data: str
- @return: an SSH signature message.
- @rtype: L{Message}
+ :param str data: the data to sign.
+ :return: an SSH signature `message <.Message>`.
"""
- return ''
+ return bytes()
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
+ :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 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
+ 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 filename: name of the file to read
- @type filename: str
- @param password: an optional password to use to decrypt the key file,
+ :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
- @type password: str
- @return: a new key object based on the given private key
- @rtype: L{PKey}
+ :return: a new `.PKey` based on the given private key
- @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
+ :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
@@ -202,22 +186,19 @@ class PKey (object):
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},
+ object. If the private key is encrypted and ``password`` is not ``None``,
the given password will be used to decrypt the key (otherwise
- L{PasswordRequiredException} is thrown).
+ `.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}
+ :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
- @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
+ :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
@@ -226,59 +207,52 @@ class PKey (object):
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.
+ ``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
+ :param str filename: name of the file to write
+ :param str password:
+ an optional password to use to encrypt the key file
- @raise IOError: if there was an error writing the file
- @raise SSHException: if the key is invalid
+ :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 C{None}, the key is encrypted before writing.
+ password is not ``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
+ :param file file_obj: the file object to write into
+ :param str password: an optional password to use to encrypt the key
- @raise IOError: if there was an error writing to the file
- @raise SSHException: if the key is invalid
+ :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
- C{"BEGIN xxx PRIVATE KEY"} for some C{xxx}, base64-decode the text we
+ ``"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
- 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()
+ ``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.
+ """
+ with open(filename, 'r') as f:
+ data = self._read_private_key(tag, f, password)
return data
def _read_private_key(self, tag, f, password=None):
@@ -303,8 +277,8 @@ class PKey (object):
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:
+ 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
@@ -315,7 +289,7 @@ class PKey (object):
try:
encryption_type, saltstr = headers['dek-info'].split(',')
except:
- raise SSHException('Can\'t parse DEK-info in private key file')
+ 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
@@ -324,8 +298,8 @@ class PKey (object):
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)
+ salt = unhexlify(b(saltstr))
+ key = util.generate_key_bytes(md5, salt, password, keysize)
return cipher.new(key, mode, salt).decrypt(data)
def _write_private_key_file(self, tag, filename, data, password=None):
@@ -335,47 +309,42 @@ class PKey (object):
a trivially-encoded format (base64) which is completely insecure. If
a password is given, DES-EDE3-CBC is used.
- @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 write.
- @type filename: str
- @param data: data blob that makes up the private key.
- @type data: str
- @param password: an optional password to use to encrypt the file.
- @type password: str
+ :param str tag: ``"RSA"`` or ``"DSA"``, the tag used to mark the data block.
+ :param file filename: name of the file to write.
+ :param str data: data blob that makes up the private key.
+ :param str password: an optional password to use to encrypt the file.
- @raise IOError: if there was an error writing the file.
+ :raises IOError: if there was an error writing the file.
"""
- f = open(filename, 'w', 0600)
- # grrr... the mode doesn't always take hold
- os.chmod(filename, 0600)
- self._write_private_key(tag, f, data, password)
- f.close()
+ with open(filename, 'w', o600) as f:
+ # grrr... the mode doesn't always take hold
+ os.chmod(filename, o600)
+ self._write_private_key(tag, f, data, password)
def _write_private_key(self, tag, f, data, password=None):
f.write('-----BEGIN %s PRIVATE KEY-----\n' % tag)
if password is not None:
# since we only support one cipher here, use it
- cipher_name = self._CIPHER_TABLE.keys()[0]
+ cipher_name = list(self._CIPHER_TABLE.keys())[0]
cipher = self._CIPHER_TABLE[cipher_name]['cipher']
keysize = self._CIPHER_TABLE[cipher_name]['keysize']
blocksize = self._CIPHER_TABLE[cipher_name]['blocksize']
mode = self._CIPHER_TABLE[cipher_name]['mode']
- salt = rng.read(8)
- key = util.generate_key_bytes(MD5, salt, password, keysize)
+ salt = os.urandom(16)
+ key = util.generate_key_bytes(md5, salt, password, keysize)
if len(data) % blocksize != 0:
n = blocksize - len(data) % blocksize
- #data += rng.read(n)
+ #data += os.urandom(n)
# that would make more sense ^, but it confuses openssh.
- data += '\0' * n
+ data += zero_byte * n
data = cipher.new(key, mode, salt).encrypt(data)
f.write('Proc-Type: 4,ENCRYPTED\n')
- f.write('DEK-Info: %s,%s\n' % (cipher_name, hexlify(salt).upper()))
+ f.write('DEK-Info: %s,%s\n' % (cipher_name, u(hexlify(salt)).upper()))
f.write('\n')
- s = base64.encodestring(data)
+ s = u(encodebytes(data))
# re-wrap to 64-char lines
s = ''.join(s.split('\n'))
- s = '\n'.join([s[i : i+64] for i in range(0, len(s), 64)])
+ s = '\n'.join([s[i: i + 64] for i in range(0, len(s), 64)])
f.write(s)
f.write('\n')
f.write('-----END %s PRIVATE KEY-----\n' % tag)