diff options
author | Robey Pointer <robey@twitter.com> | 2009-07-19 15:04:54 -0700 |
---|---|---|
committer | Robey Pointer <robey@twitter.com> | 2009-07-19 15:04:54 -0700 |
commit | ac42ba88d70ec0cc6acc1b1d5526591de5d1eae2 (patch) | |
tree | 45cd0f7fb14860937059dc5ee25ae0a38dd25278 | |
parent | a0313a47e4dd978c0d0fcae2cd308239312ffea0 (diff) |
patch for ARC4 cipher support, and CTR block chaining, from denis bernard.
-rw-r--r-- | paramiko/transport.py | 27 | ||||
-rw-r--r-- | paramiko/util.py | 34 |
2 files changed, 56 insertions, 5 deletions
diff --git a/paramiko/transport.py b/paramiko/transport.py index 5a871e95..63a2a27f 100644 --- a/paramiko/transport.py +++ b/paramiko/transport.py @@ -50,8 +50,12 @@ from paramiko.ssh_exception import SSHException, BadAuthenticationType, ChannelE # i believe this on the standards track. # PyCrypt compiled for Win32 can be downloaded from the HashTar homepage: # http://nitace.bsd.uchicago.edu:8080/hashtar -from Crypto.Cipher import Blowfish, AES, DES3 +from Crypto.Cipher import Blowfish, AES, DES3, ARC4 from Crypto.Hash import SHA, MD5 +try: + from Crypto.Util import Counter +except ImportError: + from paramiko.util import Counter # for thread cleanup @@ -196,17 +200,22 @@ class Transport (threading.Thread): _PROTO_ID = '2.0' _CLIENT_ID = 'paramiko_1.7.4' - _preferred_ciphers = ( 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc' ) + _preferred_ciphers = ( 'aes128-ctr', 'aes256-ctr', 'aes128-cbc', 'blowfish-cbc', 'aes256-cbc', '3des-cbc', + 'arcfour128', 'arcfour256' ) _preferred_macs = ( 'hmac-sha1', 'hmac-md5', 'hmac-sha1-96', 'hmac-md5-96' ) _preferred_keys = ( 'ssh-rsa', 'ssh-dss' ) _preferred_kex = ( 'diffie-hellman-group1-sha1', 'diffie-hellman-group-exchange-sha1' ) _preferred_compression = ( 'none', ) _cipher_info = { + 'aes128-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 16 }, + 'aes256-ctr': { 'class': AES, 'mode': AES.MODE_CTR, 'block-size': 16, 'key-size': 32 }, 'blowfish-cbc': { 'class': Blowfish, 'mode': Blowfish.MODE_CBC, 'block-size': 8, 'key-size': 16 }, 'aes128-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 16 }, 'aes256-cbc': { 'class': AES, 'mode': AES.MODE_CBC, 'block-size': 16, 'key-size': 32 }, '3des-cbc': { 'class': DES3, 'mode': DES3.MODE_CBC, 'block-size': 8, 'key-size': 24 }, + 'arcfour128': { 'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 16 }, + 'arcfour256': { 'class': ARC4, 'mode': None, 'block-size': 8, 'key-size': 32 }, } _mac_info = { @@ -1447,7 +1456,19 @@ class Transport (threading.Thread): def _get_cipher(self, name, key, iv): if name not in self._cipher_info: raise SSHException('Unknown client cipher ' + name) - return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv) + if name in ('arcfour128', 'arcfour256'): + # arcfour cipher + cipher = self._cipher_info[name]['class'].new(key) + # as per RFC 4345, the first 1536 bytes of keystream + # generated by the cipher MUST be discarded + cipher.encrypt(" " * 1536) + return cipher + elif name.endswith("-ctr"): + # CTR modes, we need a counter + counter = Counter.new(nbits=self._cipher_info[name]['block-size'] * 8, initial_value=util.inflate_long(iv, True)) + return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv, counter) + else: + return self._cipher_info[name]['class'].new(key, self._cipher_info[name]['mode'], iv) def _set_x11_handler(self, handler): # only called if a channel has turned on x11 forwarding diff --git a/paramiko/util.py b/paramiko/util.py index e5ab0769..2d932461 100644 --- a/paramiko/util.py +++ b/paramiko/util.py @@ -22,6 +22,7 @@ Useful functions used by the rest of paramiko. from __future__ import generators +import array from binascii import hexlify, unhexlify import sys import struct @@ -186,10 +187,10 @@ def load_host_keys(filename): return a compound dict of C{hostname -> keytype ->} L{PKey <paramiko.pkey.PKey>}. The hostname may be an IP address or DNS name. The keytype will be either C{"ssh-rsa"} or C{"ssh-dss"}. - + This type of file unfortunately doesn't exist on Windows, but on posix, it will usually be stored in C{os.path.expanduser("~/.ssh/known_hosts")}. - + Since 1.5.3, this is just a wrapper around L{HostKeys}. @param filename: name of the file to read host keys from @@ -270,3 +271,32 @@ def get_logger(name): return l +class Counter (object): + """Stateful counter for CTR mode crypto""" + def __init__(self, nbits, initial_value=1L, overflow=0L): + self.blocksize = nbits / 8 + self.overflow = overflow + # start with value - 1 so we don't have to store intermediate values when counting + # could the iv be 0? + if initial_value == 0: + self.value = array.array('c', '\xFF' * self.blocksize) + else: + x = deflate_long(initial_value - 1, add_sign_padding=False) + self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x) + + def __call__(self): + """Increament the counter and return the new value""" + i = self.blocksize - 1 + while i > -1: + c = self.value[i] = chr((ord(self.value[i]) + 1) % 256) + if c != '\x00': + return self.value.tostring() + i -= 1 + # counter reset + x = deflate_long(self.overflow, add_sign_padding=False) + self.value = array.array('c', '\x00' * (self.blocksize - len(x)) + x) + return self.value.tostring() + + def new(cls, nbits, initial_value=1L, overflow=0L): + return cls(nbits, initial_value=initial_value, overflow=overflow) + new = classmethod(new) |