briarfox
briarfox

Reputation: 525

Generating SSH keypair with paramiko in Python

I am trying to generate a SSH key pair with the python module paramiko. There doesn't seem to be much info about key generation. I've read through the paramiko docs but can't figure out whats wrong. I can generate a private and public key without password encryption. However, when I try to encrypt the private key I get the following error.

ValueError: IV must be 8 bytes long

I believe the above error is from pycrypto. I've looked through the relevant code in paramiko.pkey and pycrypto without any luck.

Here is a small example.

import paramiko

def keygen(filename,passwd=None,bits=1024):
    k = paramiko.RSAKey.generate(bits)
    #This line throws the error.
    k.write_private_key_file(filename,password = 'cleverpassword')
    o = open(fil+'.pub' ,"w").write(k.get_base64())

traceback

Traceback (most recent call last):
  File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Documents/test.py", line 14, in keygen
    k.write_private_key_file(filename,password = 'cleverpassword')
  File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/paramiko/rsakey.py", line 127, in write_private_key_file
    self._write_private_key_file('RSA', filename, self._encode_key(), password)
  File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/paramiko/pkey.py", line 323, in _write_private_key_file
    self._write_private_key(tag, f, data, password)
  File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/paramiko/pkey.py", line 341, in _write_private_key
    data = cipher.new(key, mode, salt).encrypt(data)
  File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/Crypto/Cipher/DES3.py", line 114, in new
    return DES3Cipher(key, *args, **kwargs)
  File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/Crypto/Cipher/DES3.py", line 76, in __init__
    blockalgo.BlockAlgo.__init__(self, _DES3, key, *args, **kwargs)
  File "/var/mobile/Applications/149E4C21-2F92-4712-BAC6-151A171C6687/Pythonista.app/pylib/site-packages/Crypto/Cipher/blockalgo.py", line 141, in __init__
    self._cipher = factory.new(key, *args, **kwargs)
ValueError: IV must be 8 bytes long

Upvotes: 6

Views: 6349

Answers (1)

merlin2011
merlin2011

Reputation: 75565

The Problem

This looks like a bug in paramiko.

If you look at the line that threw the error in pkey.py, it is the following line:

        data = cipher.new(key, mode, salt).encrypt(data)

Let us now look at the lines before it, which set the mode by first selecting a cipher_name.

        # since we only support one cipher here, use it
        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']

Here are the contents of _CIPHER_TABLE.

_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},
}

Observe how the comment contradicts the code. Two ciphers are available, and the line above which selects the cipher_name assumes there is only one.

Based on the error, it appears that 'DES-EDE3-CBC' is selected. If we look at the comment in DES3.py, we see the following requirement for an IV.

  IV : byte string
    The initialization vector to use for encryption or decryption.

    It is ignored for `MODE_ECB` and `MODE_CTR`.

    For `MODE_OPENPGP`, IV must be `block_size` bytes long for encryption
    and `block_size` +2 bytes for decryption (in the latter case, it is
    actually the *encrypted* IV which was prefixed to the ciphertext).
    It is mandatory.

From paramiko's source, we observe that no IV is passed, and hence the error we saw.

Workaround

Change the following line in pkey.py to hardcode the 'AES-128-CBC' cipher instead.

   # cipher_name = list(self._CIPHER_TABLE.keys())[1]
   cipher_name = 'AES-128-CBC'

Upvotes: 4

Related Questions