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(b('\n'), 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(b('')) return b('\n').join(pem_lines)
def _pad_for_signing(message, target_length): r'''Pads the message for signing, returning the padded message. The padding is always a repetition of FF bytes. :return: 00 01 PADDING 00 MESSAGE >>> block = _pad_for_signing('hello', 16) >>> len(block) 16 >>> block[0:2] '\x00\x01' >>> block[-6:] '\x00hello' >>> block[2:-6] '\xff\xff\xff\xff\xff\xff\xff\xff' ''' 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)) padding_length = target_length - msglength - 3 return b('').join( [b('\x00\x01'), padding_length * b('\xff'), b('\x00'), message])
def _pad_for_signing(message, target_length): r'''Pads the message for signing, returning the padded message. The padding is always a repetition of FF bytes. :return: 00 01 PADDING 00 MESSAGE >>> block = _pad_for_signing('hello', 16) >>> len(block) 16 >>> block[0:2] '\x00\x01' >>> block[-6:] '\x00hello' >>> block[2:-6] '\xff\xff\xff\xff\xff\xff\xff\xff' ''' 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)) padding_length = target_length - msglength - 3 return b('').join([b('\x00\x01'), padding_length * b('\xff'), b('\x00'), message])
def load_pem(contents, pem_marker): pem_start, pem_end = _markers(pem_marker) pem_lines = [] in_pem_part = False for line in contents.splitlines(): line = line.strip() if not line: continue if line == pem_start: if in_pem_part: raise ValueError('Seen start marker "%s" twice' % pem_start) in_pem_part = True continue if not in_pem_part: continue if in_pem_part and line == pem_end: in_pem_part = False break if b(':') in line: continue pem_lines.append(line) 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) pem = b('').join(pem_lines) return base64.decodestring(pem)
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 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 = b('').join(pem_lines) return base64.standard_b64decode(pem)
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.standard_b64encode(contents).replace(b('\n'), 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(b('')) return 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 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 = b('').join(pem_lines) return base64.decodestring(pem)
def test_chunk_size(self): self.assertEqual(int2bytes(123456789, 6), b('\x00\x00\x07[\xcd\x15')) self.assertEqual(int2bytes(123456789, 7), b('\x00\x00\x00\x07[\xcd\x15')) self.assertEqual(_int2bytes(123456789, 6), b('\x00\x00\x07[\xcd\x15')) self.assertEqual(_int2bytes(123456789, 7), b('\x00\x00\x00\x07[\xcd\x15'))
def _markers(pem_marker): """ Returns the start and end PEM markers """ if is_bytes(pem_marker): pem_marker = pem_marker.decode('utf-8') return (b('-----BEGIN %s-----' % pem_marker), b('-----END %s-----' % pem_marker))
def _pad_for_signing(message, target_length): 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)) padding_length = target_length - msglength - 3 return b('').join([b('\x00\x01'), padding_length * b('\xff'), b('\x00'), message])
def _markers(pem_marker): """ Returns the start and end PEM markers, as bytes. """ if not is_bytes(pem_marker): pem_marker = pem_marker.encode('ascii') return (b('-----BEGIN ') + pem_marker + b('-----'), b('-----END ') + pem_marker + b('-----'))
def test_keygen_priv_stdout(self): with captured_output() as (out, err): with cli_args(128): rsa.cli.keygen() lines = get_bytes_out(out).splitlines() self.assertEqual(b('-----BEGIN RSA PRIVATE KEY-----'), lines[0]) self.assertEqual(b('-----END RSA PRIVATE KEY-----'), lines[-1]) # The key size should be shown on stderr self.assertTrue('128-bit key' in err.getvalue())
def save_pem(contents, pem_marker): pem_start, pem_end = _markers(pem_marker) b64 = base64.encodestring(contents).replace(b('\n'), 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(b('')) return b('\n').join(pem_lines)
def _pad_for_signing(message, target_length): 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)) padding_length = target_length - msglength - 3 return b('').join( [b('\x00\x01'), padding_length * b('\xff'), b('\x00'), message])
def test_read_zero(self): encoded = b('\x00crummy') infile = BytesIO(encoded) (decoded, read) = varblock.read_varint(infile) # Test the returned values self.assertEqual(0, decoded) self.assertEqual(1, read) # The rest of the file should be untouched self.assertEqual(b('crummy'), infile.read())
def decrypt(crypto, priv_key): blocksize = common.byte_size(priv_key.n) encrypted = transform.bytes2int(crypto) decrypted = core.decrypt_int(encrypted, priv_key.d, priv_key.n) cleartext = transform.int2bytes(decrypted, blocksize) if cleartext[0:2] != b('\x00\x02'): raise DecryptionError('Decryption failed') try: sep_idx = cleartext.index(b('\x00'), 2) except ValueError: raise DecryptionError('Decryption failed') return cleartext[sep_idx + 1:]
def test_sign_verify_bigfile(self): # Large enough to store MD5-sum and ASN.1 code for MD5 pub_key, priv_key = rsa.newkeys((34 + 11) * 8) # Sign the file msgfile = BytesIO(b("123456Sybren")) signature = pkcs1.sign(msgfile, priv_key, "MD5") # Check the signature msgfile.seek(0) self.assertTrue(pkcs1.verify(msgfile, signature, pub_key)) # Alter the message, re-check msgfile = BytesIO(b("123456sybren")) self.assertRaises(pkcs1.VerificationError, pkcs1.verify, msgfile, signature, pub_key)
def verify(message, signature, pub_key): blocksize = common.byte_size(pub_key.n) encrypted = transform.bytes2int(signature) decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) clearsig = transform.int2bytes(decrypted, blocksize) if clearsig[0:2] != b('\x00\x01'): raise VerificationError('Verification failed') try: sep_idx = clearsig.index(b('\x00'), 2) except ValueError: raise VerificationError('Verification failed') method_name, signature_hash = _find_method_hash(clearsig[sep_idx + 1:]) message_hash = _hash(message, method_name) if message_hash != signature_hash: raise VerificationError('Verification failed')
def test_write_zero(self): outfile = BytesIO() written = varblock.write_varint(outfile, 0) # Test the returned values self.assertEqual(b('\x00'), outfile.getvalue()) self.assertEqual(1, written)
def test_encrypt_decrypt_bigfile(self): # Expected block size + 11 bytes padding pub_key, priv_key = rsa.newkeys((6 + 11) * 8) # Encrypt the file message = b('123456Sybren') infile = BytesIO(message) outfile = BytesIO() bigfile.encrypt_bigfile(infile, outfile, pub_key) # Test crypto = outfile.getvalue() cryptfile = BytesIO(crypto) clearfile = BytesIO() bigfile.decrypt_bigfile(cryptfile, clearfile, priv_key) self.assertEquals(clearfile.getvalue(), message) # We have 2x6 bytes in the message, so that should result in two # bigfile. cryptfile.seek(0) varblocks = list(varblock.yield_varblocks(cryptfile)) self.assertEqual(2, len(varblocks))
def int2bytes(number, fill_size=None, chunk_size=None, overflow=False): if number < 0: raise ValueError('Number must be an unsigned integer: %d' % number) if fill_size and chunk_size: raise ValueError('You can either fill or pad chunks, but not both') number & 1 raw_bytes = b('') num = number word_bits, _, max_uint, pack_type = get_word_alignment(num) pack_format = '>%s' % pack_type while num > 0: raw_bytes = pack(pack_format, num & max_uint) + raw_bytes num >>= word_bits zero_leading = bytes_leading(raw_bytes) if number == 0: raw_bytes = ZERO_BYTE raw_bytes = raw_bytes[zero_leading:] length = len(raw_bytes) if fill_size and fill_size > 0: if not overflow and length > fill_size: raise OverflowError( 'Need %d bytes for number, but fill size is %d' % (length, fill_size)) raw_bytes = raw_bytes.rjust(fill_size, ZERO_BYTE) elif chunk_size and chunk_size > 0: remainder = length % chunk_size if remainder: padding_size = chunk_size - remainder raw_bytes = raw_bytes.rjust(length + padding_size, ZERO_BYTE) return raw_bytes
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 rsa.pem.save_pem(der, b('RSA PRIVATE KEY'))
def test_sign_verify_bigfile(self): # Large enough to store MD5-sum and ASN.1 code for MD5 pub_key, priv_key = rsa.newkeys((34 + 11) * 8) # Sign the file msgfile = BytesIO(b('123456Sybren')) signature = pkcs1.sign(msgfile, priv_key, 'MD5') # Check the signature msgfile.seek(0) self.assertTrue(pkcs1.verify(msgfile, signature, pub_key)) # Alter the message, re-check msgfile = BytesIO(b('123456sybren')) self.assertRaises(pkcs1.VerificationError, pkcs1.verify, msgfile, signature, pub_key)
def verify(message, 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 = common.byte_size(pub_key.n) encrypted = transform.bytes2int(signature) decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) clearsig = transform.int2bytes(decrypted, blocksize) # If we can't find the signature marker, verification failed. if clearsig[0:2] != b('\x00\x01'): raise VerificationError('Verification failed') # Find the 00 separator between the padding and the payload try: sep_idx = clearsig.index(b('\x00'), 2) except ValueError: raise VerificationError('Verification failed') # Get the hash and the hash method (method_name, signature_hash) = _find_method_hash(clearsig[sep_idx + 1:]) message_hash = _hash(message, method_name) # Compare the real hash to the hash in the signature if message_hash != signature_hash: raise VerificationError('Verification failed') return True
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 rsa.pem.save_pem(der, b('RSA PRIVATE KEY'))
def verify(message, 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 = common.byte_size(pub_key.n) encrypted = transform.bytes2int(signature) decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n) clearsig = transform.int2bytes(decrypted, blocksize) # If we can't find the signature marker, verification failed. if clearsig[0:2] != b('\x00\x01'): raise VerificationError('Verification failed') # Find the 00 separator between the padding and the payload try: sep_idx = clearsig.index(b('\x00'), 2) except ValueError: raise VerificationError('Verification failed') # Get the hash and the hash method (method_name, signature_hash) = _find_method_hash(clearsig[sep_idx+1:]) message_hash = _hash(message, method_name) # Compare the real hash to the hash in the signature if message_hash != signature_hash: raise VerificationError('Verification failed') return True
def test_sign_different_key(self): '''Signing with another key should let the verification fail.''' (otherpub, _) = rsa.newkeys(512) message = b('je moeder') signature = pkcs1.sign(message, self.priv, 'SHA-256') self.assertRaises(pkcs1.VerificationError, pkcs1.verify, message, signature, otherpub)
def test_write_varint(self): expected = b('\xac\x02') outfile = BytesIO() written = varblock.write_varint(outfile, 300) # Test the returned values self.assertEqual(expected, outfile.getvalue()) self.assertEqual(2, written)
def test_sign_different_key(self): """Signing with another key should let the verification fail.""" (otherpub, _) = rsa.newkeys(512) message = b('je moeder') signature = pkcs1.sign(message, self.priv, 'SHA-256') self.assertRaises(pkcs1.VerificationError, pkcs1.verify, message, signature, otherpub)
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. :rtype: bytes """ der = self._save_pkcs1_der() return rsa.pem.save_pem(der, b('RSA PRIVATE KEY'))
def test_sign_verify(self): '''Test happy flow of sign and verify''' message = b('je moeder') print("\tMessage: %r" % message) signature = pkcs1.sign(message, self.priv, 'SHA-256') print("\tSignature: %r" % signature) self.assertTrue(pkcs1.verify(message, signature, self.pub))
def test_sign_verify(self): """Test happy flow of sign and verify""" message = b('je moeder') print("\tMessage: %r" % message) signature = pkcs1.sign(message, self.priv, 'SHA-256') print("\tSignature: %r" % signature) self.assertTrue(pkcs1.verify(message, signature, self.pub))
def test_sign_verify(self): '''Test happy flow of sign and verify''' message = b('je moeder') print("\tMessage: %r" % message) signature = pkcs1.sign(message, self.priv, 'SHA-256') print("\tSignature: %r" % signature) pkcs1.verify(message, signature, self.pub)
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 = rsa.pem.load_pem(keyfile, 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)) padding = b('') padding_length = target_length - msglength - 3 while len(padding) < padding_length: needed_bytes = padding_length - len(padding) new_padding = os.urandom(needed_bytes + 5) new_padding = new_padding.replace(b('\x00'), b('')) padding = padding + new_padding[:needed_bytes] return b('').join([b('\x00\x02'), padding, b('\x00'), message])
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 = rsa.pem.load_pem(keyfile, b('RSA PRIVATE KEY')) return cls._load_pkcs1_der(der)
def test_zero(self): self.assertEqual(int2bytes(0, 4), b('\x00') * 4) self.assertEqual(int2bytes(0, 7), b('\x00') * 7) self.assertEqual(int2bytes(0), b('\x00')) self.assertEqual(_int2bytes(0, 4), b('\x00') * 4) self.assertEqual(_int2bytes(0, 7), b('\x00') * 7) self.assertEqual(_int2bytes(0), b('\x00'))
def test_encrypt_decrypt(self): with open('cleartext.txt', 'wb') as outfile: outfile.write(b'Hello cleartext RSA users!') with cli_args('-i', 'cleartext.txt', '--out=encrypted.txt', self.pub_fname): with captured_output(): rsa.cli.encrypt() with cli_args('-i', 'encrypted.txt', self.priv_fname): with captured_output() as (out, err): rsa.cli.decrypt() # We should have the original cleartext on stdout now. output = get_bytes_out(out) self.assertEqual(b('Hello cleartext RSA users!'), output)
def _pad_for_encryption(message, target_length): 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)) padding = b('') padding_length = target_length - msglength - 3 while len(padding) < padding_length: needed_bytes = padding_length - len(padding) new_padding = os.urandom(needed_bytes + 5) new_padding = new_padding.replace(b('\x00'), b('')) padding = padding + new_padding[:needed_bytes] return b('').join([b('\x00\x02'), padding, b('\x00'), message])
def _pad_for_encryption(message, target_length): 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)) padding = b('') padding_length = target_length - msglength - 3 while len(padding) < padding_length: needed_bytes = padding_length - len(padding) new_padding = os.urandom(needed_bytes + 5) new_padding = new_padding.replace(b('\x00'), b('')) padding = padding + new_padding[:needed_bytes] return b('').join([b('\x00\x02'), padding, 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(b'hello', 16) >>> len(block) 16 >>> block[0:2] b'\x00\x02' >>> block[-6:] b'\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 = 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(b('\x00'), b('')) padding = padding + new_padding[:needed_bytes] assert len(padding) == padding_length return b('').join([b('\x00\x02'), padding, 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 = 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(b('\x00'), b('')) padding = padding + new_padding[:needed_bytes] assert len(padding) == padding_length return b('').join([b('\x00\x02'), padding, b('\x00'), message])
def _pad_zero(message, target_length): r'''Pads the message for encryption, returning the padded message. :return: 00 02 ZERO_BYTES 00 MESSAGE ''' 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 = 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 = b('\xff') for i in range(1,needed_bytes): new_padding = new_padding + b('\xff') padding = padding + new_padding[:needed_bytes] assert len(padding) == padding_length return b('').join([b('\x00\x02'), padding, b('\x00'), message])
def test_alter_message(self): '''Altering the message should let the verification fail.''' signature = pkcs1.sign(b('je moeder'), self.priv, 'SHA-256') self.assertRaises(pkcs1.VerificationError, pkcs1.verify, b('mijn moeder'), signature, self.pub)
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 = common.byte_size(priv_key.n) encrypted = transform.bytes2int(crypto) decrypted = core.decrypt_int(encrypted, priv_key.d, priv_key.n) cleartext = transform.int2bytes(decrypted, blocksize) # If we can't find the cleartext marker, decryption failed. if cleartext[0:2] != b('\x00\x02'): raise DecryptionError('Decryption failed') # Find the 00 separator between the padding and the message try: sep_idx = cleartext.index(b('\x00'), 2) except ValueError: raise DecryptionError('Decryption failed') return cleartext[sep_idx + 1:]
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. '''Unittest for saving and loading keys.''' import base64 import unittest import os.path import pickle from rsa._compat import b import rsa.key B64PRIV_DER = b('MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt') PRIVATE_DER = base64.standard_b64decode(B64PRIV_DER) B64PUB_DER = b('MAwCBQDeKYlRAgMBAAE=') PUBLIC_DER = base64.standard_b64decode(B64PUB_DER) PRIVATE_PEM = b(''' -----BEGIN CONFUSING STUFF----- Cruft before the key -----BEGIN RSA PRIVATE KEY----- Comment: something blah %s -----END RSA PRIVATE KEY-----