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)
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)
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:]
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'))
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:]