Ejemplo n.º 1
0
def save_pem(contents, pem_marker):
    '''Saves a PEM file.

    @param contents: the contents to encode in PEM format
    @param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
        when your file has '-----BEGIN RSA PRIVATE KEY-----' and
        '-----END RSA PRIVATE KEY-----' markers.

    @return the base64-encoded content between the start and end markers.

    '''

    (pem_start, pem_end) = _markers(pem_marker)

    b64 = base64.encodestring(contents).replace(boomCryptConvLib.b('\n'), boomCryptConvLib.b(''))
    pem_lines = [pem_start]
    
    for block_start in range(0, len(b64), 64):
        block = b64[block_start:block_start + 64]
        pem_lines.append(block)

    pem_lines.append(pem_end)
    pem_lines.append(boomCryptConvLib.b(''))

    return boomCryptConvLib.b('\n').join(pem_lines)     
Ejemplo n.º 2
0
def load_pem(contents, pem_marker):
    '''Loads a PEM file.

    @param contents: the contents of the file to interpret
    @param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
        when your file has '-----BEGIN RSA PRIVATE KEY-----' and
        '-----END RSA PRIVATE KEY-----' markers.

    @return the base64-decoded content between the start and end markers.

    @raise ValueError: when the content is invalid, for example when the start
        marker cannot be found.

    '''

    (pem_start, pem_end) = _markers(pem_marker)

    pem_lines = []
    in_pem_part = False

    for line in contents.splitlines():
        line = line.strip()

        # Skip empty lines
        if not line:
            continue

        # Handle start marker
        if line == pem_start:
            if in_pem_part:
                raise ValueError('Seen start marker "%s" twice' % pem_start)

            in_pem_part = True
            continue

        # Skip stuff before first marker
        if not in_pem_part:
            continue

        # Handle end marker
        if in_pem_part and line == pem_end:
            in_pem_part = False
            break

        # Load fields
        if boomCryptConvLib.b(':') in line:
            continue

        pem_lines.append(line)

    # Do some sanity checks
    if not pem_lines:
        raise ValueError('No PEM start marker "%s" found' % pem_start)

    if in_pem_part:
        raise ValueError('No PEM end marker "%s" found' % pem_end)

    # Base64-decode the contents
    pem = boomCryptConvLib.b('').join(pem_lines)
    return base64.decodestring(pem)
Ejemplo n.º 3
0
def _markers(pem_marker):
    '''
    Returns the start and end PEM markers
    '''

    if is_bytes(pem_marker):
        pem_marker = pem_marker.decode('utf-8')

    return (boomCryptConvLib.b('-----BEGIN %s-----' % pem_marker),
            boomCryptConvLib.b('-----END %s-----' % pem_marker)) 
def verify(signature, pub_key):
    '''Verifies that the signature matches the message.
    
    The hash method is detected automatically from the signature.
    
    :param message: the signed message. Can be an 8-bit string or a file-like
        object. If ``message`` has a ``read()`` method, it is assumed to be a
        file-like object.
    :param signature: the signature block, as created with :py:func:`rsa.sign`.
    :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
    :raise VerificationError: when the signature doesn't match the message.

    .. warning::

        Never display the stack trace of a
        :py:class:`rsa.pkcs1.VerificationError` exception. It shows where in
        the code the exception occurred, and thus leaks information about the
        key. It's only a tiny bit of information, but every bit makes cracking
        the keys easier.

    '''

    blocksize = byte_size(pub_key.n)
    encrypted = boomCryptConvLib.bytes2int(signature)
    decrypted = decrypt_int(encrypted, pub_key.e, pub_key.n)
    clearsig = boomCryptConvLib.int2bytes(decrypted, blocksize)
   
    # Find the 00 separator between the padding and the payload
    try:
        sep_idx = clearsig.index(boomCryptConvLib.b('\x00'), 2)
    except ValueError:
        raise DecryptionError('Verification failed')
    
    return clearsig[sep_idx+1:]
def verify(signature, pub_key):
    '''Verifies that the signature matches the message.
    
    The hash method is detected automatically from the signature.
    
    :param message: the signed message. Can be an 8-bit string or a file-like
        object. If ``message`` has a ``read()`` method, it is assumed to be a
        file-like object.
    :param signature: the signature block, as created with :py:func:`rsa.sign`.
    :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
    :raise VerificationError: when the signature doesn't match the message.

    .. warning::

        Never display the stack trace of a
        :py:class:`rsa.pkcs1.VerificationError` exception. It shows where in
        the code the exception occurred, and thus leaks information about the
        key. It's only a tiny bit of information, but every bit makes cracking
        the keys easier.

    '''

    blocksize = byte_size(pub_key.n)
    encrypted = boomCryptConvLib.bytes2int(signature)
    decrypted = decrypt_int(encrypted, pub_key.e, pub_key.n)
    clearsig = boomCryptConvLib.int2bytes(decrypted, blocksize)

    # Find the 00 separator between the padding and the payload
    try:
        sep_idx = clearsig.index(boomCryptConvLib.b('\x00'), 2)
    except ValueError:
        raise DecryptionError('Verification failed')

    return clearsig[sep_idx + 1:]
Ejemplo n.º 6
0
    def _save_pkcs1_pem(self):
        '''Saves a PKCS#1 PEM-encoded private key file.

        @return: contents of a PEM-encoded file that contains the private key.
        '''

        der = self._save_pkcs1_der()
        return save_pem(der, boomCryptConvLib.b('RSA PRIVATE KEY'))
Ejemplo n.º 7
0
    def _load_pkcs1_pem(cls, keyfile):
        '''Loads a PKCS#1 PEM-encoded private key file.

        The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and
        after the "-----END RSA PRIVATE KEY-----" lines is ignored.

        @param keyfile: contents of a PEM-encoded file that contains the private
            key.
        @return: a PrivateKey object
        '''

        der = load_pem(keyfile, boomCryptConvLib.b('RSA PRIVATE KEY'))
        return cls._load_pkcs1_der(der)
def _pad_for_encryption(message, target_length):
    r'''Pads the message for encryption, returning the padded message.
    
    :return: 00 02 RANDOM_DATA 00 MESSAGE
    
    >>> block = _pad_for_encryption('hello', 16)
    >>> len(block)
    16
    >>> block[0:2]
    '\x00\x02'
    >>> block[-6:]
    '\x00hello'

    '''

    max_msglength = target_length - 11
    msglength = len(message)

    if msglength > max_msglength:
        raise OverflowError('%i bytes needed for message, but there is only'
                            ' space for %i' % (msglength, max_msglength))

    # Get random padding
    padding = boomCryptConvLib.b('')
    padding_length = target_length - msglength - 3

    # We remove 0-bytes, so we'll end up with less padding than we've asked for,
    # so keep adding data until we're at the correct length.
    while len(padding) < padding_length:
        needed_bytes = padding_length - len(padding)

        # Always read at least 8 bytes more than we need, and trim off the rest
        # after removing the 0-bytes. This increases the chance of getting
        # enough bytes, especially when needed_bytes is small
        new_padding = os.urandom(needed_bytes + 5)
        new_padding = new_padding.replace(boomCryptConvLib.b('\x00'),
                                          boomCryptConvLib.b(''))
        padding = padding + new_padding[:needed_bytes]

    assert len(padding) == padding_length

    return boomCryptConvLib.b('').join([
        boomCryptConvLib.b('\x00\x02'), padding,
        boomCryptConvLib.b('\x00'), message
    ])
def _pad_for_encryption(message, target_length):
    r'''Pads the message for encryption, returning the padded message.
    
    :return: 00 02 RANDOM_DATA 00 MESSAGE
    
    >>> block = _pad_for_encryption('hello', 16)
    >>> len(block)
    16
    >>> block[0:2]
    '\x00\x02'
    >>> block[-6:]
    '\x00hello'

    '''

    max_msglength = target_length - 11
    msglength = len(message)
    
    if msglength > max_msglength:
        raise OverflowError('%i bytes needed for message, but there is only'
            ' space for %i' % (msglength, max_msglength))
    
    # Get random padding
    padding = boomCryptConvLib.b('')
    padding_length = target_length - msglength - 3
    
    # We remove 0-bytes, so we'll end up with less padding than we've asked for,
    # so keep adding data until we're at the correct length.
    while len(padding) < padding_length:
        needed_bytes = padding_length - len(padding)
        
        # Always read at least 8 bytes more than we need, and trim off the rest
        # after removing the 0-bytes. This increases the chance of getting
        # enough bytes, especially when needed_bytes is small
        new_padding = os.urandom(needed_bytes + 5)
        new_padding = new_padding.replace(boomCryptConvLib.b('\x00'), boomCryptConvLib.b(''))
        padding = padding + new_padding[:needed_bytes]
    
    assert len(padding) == padding_length
    
    return boomCryptConvLib.b('').join([boomCryptConvLib.b('\x00\x02'),
                    padding,
                    boomCryptConvLib.b('\x00'),
                    message])    
def decrypt(crypto, priv_key):
    r'''Decrypts the given message using PKCS#1 v1.5
    
    The decryption is considered 'failed' when the resulting cleartext doesn't
    start with the bytes 00 02, or when the 00 byte between the padding and
    the message cannot be found.
    
    :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
    :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
    :raise DecryptionError: when the decryption fails. No details are given as
        to why the code thinks the decryption fails, as this would leak
        information about the private key.


    >>> import rsa
    >>> (pub_key, priv_key) = rsa.newkeys(256)

    It works with strings:

    >>> crypto = encrypt('hello', pub_key)
    >>> decrypt(crypto, priv_key)
    'hello'
    
    And with binary data:

    >>> crypto = encrypt('\x00\x00\x00\x00\x01', pub_key)
    >>> decrypt(crypto, priv_key)
    '\x00\x00\x00\x00\x01'

    Altering the encrypted information will *likely* cause a
    :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
    :py:func:`rsa.sign`.


    .. warning::

        Never display the stack trace of a
        :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
        code the exception occurred, and thus leaks information about the key.
        It's only a tiny bit of information, but every bit makes cracking the
        keys easier.

    >>> crypto = encrypt('hello', pub_key)
    >>> crypto = crypto[0:5] + 'X' + crypto[6:] # change a byte
    >>> decrypt(crypto, priv_key)
    Traceback (most recent call last):
    ...
    DecryptionError: Decryption failed

    '''
    
    blocksize = byte_size(priv_key.n)
    encrypted = boomCryptConvLib.bytes2int(crypto)
    decrypted = decrypt_int(encrypted, priv_key.d, priv_key.n)
    cleartext = boomCryptConvLib.int2bytes(decrypted, blocksize)

    # If we can't find the cleartext marker, decryption failed.
    if cleartext[0:2] != boomCryptConvLib.b('\x00\x02'):
        raise DecryptionError('Decryption failed 1')
    
    # Find the 00 separator between the padding and the message
    try:
        sep_idx = cleartext.index(boomCryptConvLib.b('\x00'), 2)
    except ValueError:
        raise DecryptionError('Decryption failed 2')
    
    return cleartext[sep_idx+1:]
def decrypt(crypto, priv_key):
    r'''Decrypts the given message using PKCS#1 v1.5
    
    The decryption is considered 'failed' when the resulting cleartext doesn't
    start with the bytes 00 02, or when the 00 byte between the padding and
    the message cannot be found.
    
    :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
    :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
    :raise DecryptionError: when the decryption fails. No details are given as
        to why the code thinks the decryption fails, as this would leak
        information about the private key.


    >>> import rsa
    >>> (pub_key, priv_key) = rsa.newkeys(256)

    It works with strings:

    >>> crypto = encrypt('hello', pub_key)
    >>> decrypt(crypto, priv_key)
    'hello'
    
    And with binary data:

    >>> crypto = encrypt('\x00\x00\x00\x00\x01', pub_key)
    >>> decrypt(crypto, priv_key)
    '\x00\x00\x00\x00\x01'

    Altering the encrypted information will *likely* cause a
    :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
    :py:func:`rsa.sign`.


    .. warning::

        Never display the stack trace of a
        :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
        code the exception occurred, and thus leaks information about the key.
        It's only a tiny bit of information, but every bit makes cracking the
        keys easier.

    >>> crypto = encrypt('hello', pub_key)
    >>> crypto = crypto[0:5] + 'X' + crypto[6:] # change a byte
    >>> decrypt(crypto, priv_key)
    Traceback (most recent call last):
    ...
    DecryptionError: Decryption failed

    '''

    blocksize = byte_size(priv_key.n)
    encrypted = boomCryptConvLib.bytes2int(crypto)
    decrypted = decrypt_int(encrypted, priv_key.d, priv_key.n)
    cleartext = boomCryptConvLib.int2bytes(decrypted, blocksize)

    # If we can't find the cleartext marker, decryption failed.
    if cleartext[0:2] != boomCryptConvLib.b('\x00\x02'):
        raise DecryptionError('Decryption failed 1')

    # Find the 00 separator between the padding and the message
    try:
        sep_idx = cleartext.index(boomCryptConvLib.b('\x00'), 2)
    except ValueError:
        raise DecryptionError('Decryption failed 2')

    return cleartext[sep_idx + 1:]