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 = 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) if len(data) % blocksize != 0: n = blocksize - len(data) % blocksize # data += rng.read(n) # that would make more sense ^, but it confuses openssh. data += "\0" * 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("\n") s = base64.encodestring(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)]) f.write(s) f.write("\n") f.write("-----END %s PRIVATE KEY-----\n" % tag)
def _write_private_key(self, tag, f, data, password=None): if not isinstance(tag, bytes): tag = tag.encode('ascii') f.write(b'-----BEGIN ' + tag + b' PRIVATE KEY-----\n') if password is not None: if not isinstance(password, bytes): password = password.encode('latin-1') # since we only support one cipher here, use it cipher_name = list(self._CIPHER_TABLE.keys())[0] cipher = self._CIPHER_TABLE[cipher_name][b'cipher'] keysize = self._CIPHER_TABLE[cipher_name][b'keysize'] blocksize = self._CIPHER_TABLE[cipher_name][b'blocksize'] mode = self._CIPHER_TABLE[cipher_name][b'mode'] salt = rng.read(8) key = util.generate_key_bytes(MD5, salt, password, keysize) if len(data) % blocksize != 0: n = blocksize - len(data) % blocksize #data += rng.read(n) # that would make more sense ^, but it confuses openssh. data += b'\0' * n data = cipher.new(key, mode, salt).encrypt(data) f.write(b'Proc-Type: 4,ENCRYPTED\n') f.write(b'DEK-Info: ' + cipher_name + ',' + hexlify(salt).upper() +b'\n') f.write(b'\n') s = base64.encodestring(data) # re-wrap to 64-char lines s = b''.join(s.split(b'\n')) s = b'\n'.join([s[i : i+64] for i in range(0, len(s), 64)]) f.write(s) f.write(b'\n') f.write(b'-----END ' + tag + b' PRIVATE KEY-----\n')
def test_1_generate_key_bytes(self): from Crypto.Hash import MD5 key = util.generate_key_bytes(MD5, '\x01\x02\x03\x04', 'happy birthday', 30) exp = unhexlify( '61E1F272F4C1C4561586BD322498C0E924672780F47BB37DDA7D54019E64') self.assertEquals(exp, key)
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 = 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) if len(data) % blocksize != 0: n = blocksize - len(data) % blocksize #data += rng.read(n) # that would make more sense ^, but it confuses openssh. data += '\0' * 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('\n') s = base64.encodestring(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)]) f.write(s) f.write('\n') f.write('-----END %s PRIVATE KEY-----\n' % tag)
def _read_private_key(self, tag, f, password=None): lines = f.readlines() start = 0 if not isinstance(tag, bytes): tag = tag.encode('ascii') if password is not None and not isinstance(password, bytes): password = password.encode('latin-1') while (start < len(lines)) and (lines[start].strip() != b'-----BEGIN ' + tag + b' PRIVATE KEY-----'): start += 1 if start >= len(lines): raise SSHException('not a valid ' + tag.decode('ascii') + ' private key file') # parse any headers first headers = {} start += 1 while start < len(lines): l = lines[start].split(b': ') if len(l) == 1: break headers[l[0].lower()] = l[1].strip() start += 1 # find end end = start while (lines[end].strip() != b'-----END ' + tag + b' 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(b''.join(lines[start:end])) except base64.binascii.Error as e: raise SSHException('base64 decoding error: ' + str(e)) if b'proc-type' not in headers: # unencryped: done return data # encrypted keyfile: will need a password if headers[b'proc-type'] != b'4,ENCRYPTED': raise SSHException('Unknown private key structure "%s"' % headers[b'proc-type']) try: encryption_type, saltstr = headers[b'dek-info'].split(b',') 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][b'cipher'] keysize = self._CIPHER_TABLE[encryption_type][b'keysize'] mode = self._CIPHER_TABLE[encryption_type][b'mode'] salt = unhexlify(saltstr) key = util.generate_key_bytes(MD5, salt, password, keysize) return cipher.new(key, mode, salt).decrypt(data)
def test_1_generate_key_bytes(self): from Crypto.Hash import MD5 key = util.generate_key_bytes(MD5, b"\x01\x02\x03\x04", b"happy birthday", 30) exp = unhexlify(b"61E1F272F4C1C4561586BD322498C0E924672780F47BB37DDA7D54019E64") self.assertEquals(exp, key)
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 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, rng, data): """ Sign a blob of data with this private key, and return a L{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} """ 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)