From dc13538d6a72ac84c21e05e9a79fb3df044b24b8 Mon Sep 17 00:00:00 2001 From: "Sybren A. St?vel" Date: Sun, 31 Jul 2011 17:44:44 +0200 Subject: More documentation --- doc/compatibility.rst | 1 + doc/conf.py | 6 ++-- doc/index.rst | 1 + doc/intro.rst | 2 +- doc/reference.rst | 29 +++++++++++++++++++ doc/usage.rst | 80 ++++++++++++++++++++++++++++++++++++++++++++++++--- rsa/key.py | 60 +++++++++++++++++++++----------------- rsa/pkcs1.py | 71 +++++++++++++++++++++++---------------------- 8 files changed, 182 insertions(+), 68 deletions(-) create mode 100644 doc/reference.rst diff --git a/doc/compatibility.rst b/doc/compatibility.rst index cbc0eb3..0cb2ba6 100644 --- a/doc/compatibility.rst +++ b/doc/compatibility.rst @@ -42,3 +42,4 @@ through the ``pyrsa-priv2pub`` command:: --out=OUTFILENAME Output filename. Writes to stdout of not specified --inform=INFORM key format of input - default PEM --outform=OUTFORM key format of output - default PEM + diff --git a/doc/conf.py b/doc/conf.py index 3ee83f1..ad49886 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -26,7 +26,9 @@ import sys, os # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'sphinx.ext.pngmath'] + 'sphinx.ext.coverage', 'sphinx.ext.pngmath'] + +# I would like to add 'sphinx.ext.viewcode', but it causes a UnicodeDecodeError # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -35,7 +37,7 @@ templates_path = ['_templates'] source_suffix = '.rst' # The encoding of source files. -#source_encoding = 'utf-8-sig' +source_encoding = 'utf-8' # The master toctree document. master_doc = 'index' diff --git a/doc/index.rst b/doc/index.rst index addc869..df81a77 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -39,6 +39,7 @@ Contents licence usage compatibility + reference Indices and tables diff --git a/doc/intro.rst b/doc/intro.rst index e27a7a2..cc89a19 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -14,6 +14,6 @@ numbers. It also included generating public and private keys. There was no functionality for working with byte sequences (such as files) yet. -.. TODO:: write more history +.. todo:: write more history diff --git a/doc/reference.rst b/doc/reference.rst new file mode 100644 index 0000000..69a89bc --- /dev/null +++ b/doc/reference.rst @@ -0,0 +1,29 @@ +Reference +================================================== + +Functions +-------------------------------------------------- + +.. autofunction:: rsa.encrypt + +.. autofunction:: rsa.decrypt + +.. autofunction:: rsa.sign + +.. autofunction:: rsa.verify + +.. autofunction:: rsa.newkeys(keysize) + +Classes +-------------------------------------------------- + +.. autoclass:: rsa.PublicKey + :members: + :inherited-members: + +.. autoclass:: rsa.PrivateKey + :members: + :inherited-members: + + + diff --git a/doc/usage.rst b/doc/usage.rst index bd499a9..61121e0 100644 --- a/doc/usage.rst +++ b/doc/usage.rst @@ -3,10 +3,6 @@ Usage This section describes the usage of the Python-RSA module. - -Generating keys --------------------------------------------------- - Before you can use RSA you need keys. You will receive a private key and a public key. @@ -15,10 +11,86 @@ and a public key. The private key is called *private* for a reason. Never share this key with anyone. +The public key is used for encypting a message such that it can only +be read by the owner of the private key. As such it's also referred to +as the *encryption key*. Decrypting a message can only be done using +the private key, hence it's also called the *decryption key*. + +The private key is used for signing a message. With this signature and +the public key, the receiver can verifying that a message was signed +by the owner of the private key, and that the message was not modified +after signing. + +Generating keys +-------------------------------------------------- + +You can use the :py:func:`rsa.newkeys` function to create a keypair. +Alternatively you can use :py:func:`rsa.PrivateKey.load_pkcs1` and +:py:func:`rsa.PublicKey.load_pkcs1` to load keys from a file. + +Generating a keypair may take a long time, depending on the number of +bits required. The number of bits determines the cryptographic +strength of the key, as well as the size of the message you can +encrypt. If you don't mind having a slightly smaller key than you +requested, you can pass ``accurate=False`` to speed up the key +generation process. + +These are some timings from my netbook (Linux 2.6, 1.6 GHz Intel Atom +N270 CPU, 2 GB RAM): + ++----------------+------------------+ +| Keysize (bits) | Time to generate | ++================+==================+ +| 32 | 0.01 sec. | ++----------------+------------------+ +| 64 | 0.03 sec. | ++----------------+------------------+ +| 96 | 0.04 sec. | ++----------------+------------------+ +| 128 | 0.08 sec. | ++----------------+------------------+ +| 256 | 0.27 sec. | ++----------------+------------------+ +| 384 | 0.93 sec. | ++----------------+------------------+ +| 512 | 1.21 sec. | ++----------------+------------------+ +| 1024 | 7.93 sec. | ++----------------+------------------+ +| 2048 | 132.97 sec. | ++----------------+------------------+ + Encryption and decryption -------------------------------------------------- +To encrypt or decrypt a message, use :py:func:`rsa.encrypt` resp. +:py:func:`rsa.decrypt`. Let's say that Alice wants to send a message +that only Bob can read. + +#. Bob generates a keypair, and gives the public key to Alice. This is + done such that Alice knows for sure that the key is really Bob's + (for example by handing over a USB stick that contains the key). + +#. Alice writes a message + +#. Alice encrypts the message using Bob's public key, and sends the + encrypted message. + +#. Bob receives the message, and decrypts it with his private key. + +Since Bob kept his private key *private*, Alice can be sure that he is +the only one who can read the message. Bob does *not* know for sure +that it was Alice that sent the message, since she didn't sign it. + + +Low-level operations +++++++++++++++++++++++++++++++ + +The core RSA algorithm operates on large integers. These operations +are considered low-level and are supported by the +:py:func:`rsa.core.encrypt_int` and :py:func:`rsa.core.decrypt_int` +functions. Signing and verification -------------------------------------------------- diff --git a/rsa/key.py b/rsa/key.py index e8fb63f..2d2ca4d 100644 --- a/rsa/key.py +++ b/rsa/key.py @@ -41,15 +41,17 @@ class AbstractKey(object): def load_pkcs1(cls, keyfile, format='PEM'): r'''Loads a key in PKCS#1 DER or PEM format. - @param keyfile: contents of a DER- or PEM-encoded file that contains + :param keyfile: contents of a DER- or PEM-encoded file that contains the public key. - @param format: the format of the file to load; 'PEM' or 'DER' - @return: a PublicKey object + :param format: the format of the file to load; 'PEM' or 'DER' + + :return: a PublicKey object + ''' methods = { - 'PEM': cls.load_pkcs1_pem, - 'DER': cls.load_pkcs1_der, + 'PEM': cls._load_pkcs1_pem, + 'DER': cls._load_pkcs1_der, } if format not in methods: @@ -63,13 +65,14 @@ class AbstractKey(object): def save_pkcs1(self, format='PEM'): '''Saves the public key in PKCS#1 DER or PEM format. - @param format: the format to save; 'PEM' or 'DER' - @returns: the DER- or PEM-encoded public key. + :param format: the format to save; 'PEM' or 'DER' + :returns: the DER- or PEM-encoded public key. + ''' methods = { - 'PEM': self.save_pkcs1_pem, - 'DER': self.save_pkcs1_der, + 'PEM': self._save_pkcs1_pem, + 'DER': self._save_pkcs1_der, } if format not in methods: @@ -86,7 +89,8 @@ class PublicKey(AbstractKey): This key is also known as the 'encryption key'. It contains the 'n' and 'e' values. - Supports attributes as well as dictionary-like access. + Supports attributes as well as dictionary-like access. Attribute accesss is + faster, though. >>> PublicKey(5, 3) PublicKey(5, 3) @@ -128,7 +132,7 @@ class PublicKey(AbstractKey): return not (self == other) @classmethod - def load_pkcs1_der(cls, keyfile): + def _load_pkcs1_der(cls, keyfile): r'''Loads a key in PKCS#1 DER format. @param keyfile: contents of a DER-encoded file that contains the public @@ -160,7 +164,7 @@ class PublicKey(AbstractKey): as_ints = tuple(int(x) for x in priv) return cls(*as_ints) - def save_pkcs1_der(self): + def _save_pkcs1_der(self): '''Saves the public key in PKCS#1 DER format. @returns: the DER-encoded public key. @@ -183,7 +187,7 @@ class PublicKey(AbstractKey): return encoder.encode(asn_key) @classmethod - def load_pkcs1_pem(cls, keyfile): + def _load_pkcs1_pem(cls, keyfile): '''Loads a PKCS#1 PEM-encoded public key file. The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and @@ -197,7 +201,7 @@ class PublicKey(AbstractKey): der = rsa.pem.load_pem(keyfile, 'RSA PUBLIC KEY') return cls.load_pkcs1_der(der) - def save_pkcs1_pem(self): + def _save_pkcs1_pem(self): '''Saves a PKCS#1 PEM-encoded public key file. @return: contents of a PEM-encoded file that contains the public key. @@ -212,7 +216,8 @@ class PrivateKey(AbstractKey): This key is also known as the 'decryption key'. It contains the 'n', 'e', 'd', 'p', 'q' and other values. - Supports attributes as well as dictionary-like access. + Supports attributes as well as dictionary-like access. Attribute accesss is + faster, though. >>> PrivateKey(3247, 65537, 833, 191, 17) PrivateKey(3247, 65537, 833, 191, 17) @@ -290,7 +295,7 @@ class PrivateKey(AbstractKey): return not (self == other) @classmethod - def load_pkcs1_der(cls, keyfile): + def _load_pkcs1_der(cls, keyfile): r'''Loads a key in PKCS#1 DER format. @param keyfile: contents of a DER-encoded file that contains the private @@ -334,7 +339,7 @@ class PrivateKey(AbstractKey): as_ints = tuple(int(x) for x in priv[1:9]) return cls(*as_ints) - def save_pkcs1_der(self): + def _save_pkcs1_der(self): '''Saves the private key in PKCS#1 DER format. @returns: the DER-encoded private key. @@ -371,7 +376,7 @@ class PrivateKey(AbstractKey): return encoder.encode(asn_key) @classmethod - def load_pkcs1_pem(cls, keyfile): + 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 @@ -385,7 +390,7 @@ class PrivateKey(AbstractKey): der = rsa.pem.load_pem(keyfile, 'RSA PRIVATE KEY') return cls.load_pkcs1_der(der) - def save_pkcs1_pem(self): + 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. @@ -535,15 +540,16 @@ def gen_keys(nbits, accurate=True): def newkeys(nbits, accurate=True): """Generates public and private keys, and returns them as (pub, priv). - The public key is also known as the 'encryption key', and is a PublicKey - object. The private key is also known as the 'decryption key' and is a - PrivateKey object. - - @param nbits: the number of bits required to store ``n = p*q``. - @param accurate: when True, ``n`` will have exactly the number of bits you - asked for. However, this makes key generation much slower. + The public key is also known as the 'encryption key', and is a + :py:class:`PublicKey` object. The private key is also known as the + 'decryption key' and is a :py:class:`PrivateKey` object. + + :param nbits: the number of bits required to store ``n = p*q``. + :param accurate: when True, ``n`` will have exactly the number of bits you + asked for. However, this makes key generation much slower. When False, + `n`` may have slightly less bits. - @return: a tuple (PublicKey, PrivateKey) + :returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`) """ diff --git a/rsa/pkcs1.py b/rsa/pkcs1.py index 3955c30..7612b27 100644 --- a/rsa/pkcs1.py +++ b/rsa/pkcs1.py @@ -14,9 +14,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -'''Functions for PKCS1 version 1.5 encryption and signing +'''Functions for PKCS#1 version 1.5 encryption and signing -This module implements certain functionality from PKCS1 version 1.5. For a +This module implements certain functionality from PKCS#1 version 1.5. For a very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes At least 8 bytes of random padding is used when encrypting a message. This makes @@ -62,7 +62,7 @@ class VerificationError(CryptoError): def _pad_for_encryption(message, target_length): r'''Pads the message for encryption, returning the padded message. - @return: 00 02 RANDOM_DATA 00 MESSAGE + :return: 00 02 RANDOM_DATA 00 MESSAGE >>> block = _pad_for_encryption('hello', 16) >>> len(block) @@ -110,7 +110,7 @@ def _pad_for_signing(message, target_length): The padding is always a repetition of FF bytes. - @return: 00 01 PADDING 00 MESSAGE + :return: 00 01 PADDING 00 MESSAGE >>> block = _pad_for_signing('hello', 16) >>> len(block) @@ -140,14 +140,13 @@ def _pad_for_signing(message, target_length): def encrypt(message, pub_key): - '''Encrypts the given message using PKCS1 v1.5 + '''Encrypts the given message using PKCS#1 v1.5 - @param message: the message to encrypt. Must be a byte string no longer than + :param message: the message to encrypt. Must be a byte string no longer than ``k-11`` bytes, where ``k`` is the number of bytes needed to encode the ``n`` component of the public key. - @param pub_key: the public key to encrypt with. - - @raise OverflowError: when the message is too large to fit in the padded + :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with. + :raise OverflowError: when the message is too large to fit in the padded block. >>> from rsa import key, common @@ -156,6 +155,7 @@ def encrypt(message, pub_key): >>> crypto = encrypt(message, pub_key) The crypto text should be just as long as the public key 'n' component: + >>> len(crypto) == common.byte_size(pub_key.n) True @@ -171,28 +171,32 @@ def encrypt(message, pub_key): return block def decrypt(crypto, priv_key): - r'''Decrypts the given message using PKCS1 v1.5 + 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 ``encrypt(message, pub_key)`` - @param priv_key: the private key to decrypt with. - - @raise DecryptionError: when the decryption fails. No details are given as + :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. - >>> from rsa import key, common - >>> (pub_key, priv_key) = key.newkeys(256) + + >>> import rsa + >>> (pub_key, priv_key) = rsa.newkeys(256) It works with strings: - >>> decrypt(encrypt('hello', pub_key), priv_key) + + >>> crypto = encrypt('hello', pub_key) + >>> decrypt(crypto, priv_key) 'hello' And with binary data: - >>> decrypt(encrypt('\x00\x00\x00\x00\x01', pub_key), priv_key) + + >>> crypto = encrypt('\x00\x00\x00\x00\x01', pub_key) + >>> decrypt(crypto, priv_key) '\x00\x00\x00\x00\x01' ''' @@ -218,16 +222,14 @@ def sign(message, priv_key, hash): '''Signs the message with the private key. Hashes the message, then signs the hash with the given key. This is known - as a "detached signature", because the message itself isn't signed. + as a "detached signature", because the message itself isn't altered. - @param message: the message to sign - @param priv_key: the private key to sign with - @param hash: the hash method used on the message. Use 'MD5', 'SHA-1', + :param message: the message to sign + :param priv_key: the :py:class:`rsa.PrivateKey` to sign with + :param hash: the hash method used on the message. Use 'MD5', 'SHA-1', 'SHA-256', 'SHA-384' or 'SHA-512'. - - @return: a message signature block. - - @raise OverflowError: if the private key is too small to contain the + :return: a message signature block. + :raise OverflowError: if the private key is too small to contain the requested hash. ''' @@ -256,11 +258,11 @@ def verify(message, signature, pub_key): The hash method is detected automatically from the signature. - @param message: the signed message - @param signature: the signature block, as created with ``sign(...)``. - @param pub_key: the public key of the person signing the message. - - @raise VerificationError: when the signature doesn't match the message. + :param message: the signed message + :param signature: the signature block, as created with ``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. + ''' blocksize = common.byte_size(pub_key.n) @@ -301,13 +303,14 @@ def _hash(message, method_name): def _find_method_hash(method_hash): '''Finds the hash method and the hash itself. - @param method_hash: ASN1 code for the hash method concatenated with the + :param method_hash: ASN1 code for the hash method concatenated with the hash itself. - @return: tuple (method, hash) where ``method`` is the used hash method, and + :return: tuple (method, hash) where ``method`` is the used hash method, and ``hash`` is the hash itself. - @raise VerificationFailed: when the hash method cannot be found + :raise VerificationFailed: when the hash method cannot be found + ''' for (hashname, asn1code) in HASH_ASN1.iteritems(): -- cgit v1.2.1