A Working Intro to Cryptography
A Working Intro to Cryptography
Kyle Isom
Buy on Leanpub

Introduction

Recently at work I have been using the PyCrypto libraries quite a bit. The documentation is pretty good, but there are a few areas that took me a bit to figure out. In this post, I’ll be writing up a quick overview of the PyCrypto library and cover some general things to know when writing cryptographic code in general. I’ll go over symmetric, public-key, hybrid, and message authentication codes. Keep in mind this is a quick introduction and a lot of gross simplifications are made. For a more complete introduction to cryptography, take a look at the references at the end of this article. This article is just an appetite-whetter - if you have a real need for information security you should hire an expert. Real data security goes beyond this quick introduction (you wouldn’t trust the design and engineering of a bridge to a student with a quick introduction to civil engineering, would you?)

Some quick terminology: for those unfamiliar, I introduce the following terms:

  • plaintext: the original message
  • ciphertext: the message after cryptographic transformations are applied to obscure the original message.
  • encrypt: producing ciphertext by applying cryptographic transformations to plaintext.
  • decrypt: producing plaintext by applying cryptographic transformations to ciphertext.
  • cipher: a particular set of cryptographic transformations providing means of both encryption and decryption.
  • hash: a set of cryptographic transformations that take a large input and transform it to a unique (typically fixed-size) output. For hashes to be cryptographically secure, collisions should be practically nonexistent. It should be practically impossible to determine the input from the output.

Cryptography is an often misunderstood component of information security, so an overview of what it is and what role it plays is in order. There are four major roles that cryptography plays:

  • confidentiality: ensuring that only the intended recipients receive the plaintext of the message.
  • data integrity: the plaintext message arrives unaltered.
  • entity authentication: the identity of the sender is verified. An entity may be a person or a machine.
  • message authentication: the message is verified as having been unaltered.

Note that cryptography is used to obscure the contents of a message and verify its contents and source. It will not hide the fact that two entities are communicating.

There are two basic types of ciphers: symmetric and public-key ciphers. A symmetric key cipher employs the use of shared secret keys. They also tend to be much faster than public-key ciphers. A public-key cipher is so-called because each key consists of a private key which is used to generate a public key. Like their names imply, the private key is kept secret while the public key is passed around. First, I’ll take a look at a specific type of symmetric ciphers: block ciphers.

Block Ciphers

There are two further types of symmetric keys: stream and block ciphers. Stream ciphers operate on data streams, i.e. one byte at a time. Block ciphers operate on blocks of data, typically 16 bytes at a time. The most common block cipher and the standard one you should use unless you have a very good reason to use another one is the AES block cipher, also documented in FIPS PUB 197. AES is a specific subset of the Rijndael cipher. AES uses block size of 128-bits (16 bytes); data should be padded out to fit the block size - the length of the data block must be multiple of the block size. For example, given an input of ABCDABCDABCDABCD ABCDABCDABCDABCD no padding would need to be done. However, given ABCDABCDABCDABCD ABCDABCDABCD an additional 4 bytes of padding would need to be added. A common padding scheme is to use 0x80 as the first byte of padding, with 0x00 bytes filling out the rest of the padding. With padding, the previous example would look like: ABCDABCDABCDABCD ABCDABCDABCD\x80\x00\x00\x00.

Here’s our padding function:

 1 def pad_data(data):
 2     # return data if no padding is required
 3     if len(data) % 16 == 0: 
 4         return data
 5 
 6     # subtract one byte that should be the 0x80
 7     # if 0 bytes of padding are required, it means only
 8     # a single \x80 is required.
 9 
10     padding_required     = 15 - (len(data) % 16)
11 
12     data = '%s\x80' % data
13     data = '%s%s' % (data, '\x00' * padding_required)
14 
15     return data

Our function to remove padding is similar:

1 def unpad_data(data):
2     if not data: 
3         return data
4  
5     data = data.rstrip('\x00')
6     if data[-1] == '\x80':
7         return data[:-1]
8     else:
9         return data

Encryption with a block cipher requires selecting a block mode. By far the most common mode used is cipher block chaining or CBC mode. Other modes include counter (CTR), cipher feedback (CFB), and the extremely insecure electronic codebook (ECB). CBC mode is the standard and is well-vetted, so I will stick to that in this tutorial. Cipher block chaining works by XORing the previous block of ciphertext with the current block. You might recognise that the first block has nothing to be XOR’d with; enter the initialisation vector. This comprises a number of randomly-generated bytes of data the same size as the cipher’s block size. This initialisation vector should random enough that it cannot be recovered.

One of the most critical components to encryption is properly generating random data. Fortunately, most of this is handled by the PyCrypto library’s Crypto.Random.OSRNG module. You should know that the more entropy sources that are available (such as network traffic and disk activity), the faster the system can generate cryptographically-secure random data. I’ve written a function that can generate a nonce suitable for use as an initialisation vector. This will work on a UNIX machine; the comments note how easy it is to adapt it to a Windows machine. This function requires a version of PyCrypto at least 2.1.0 or higher.

1 import Crypto.Random.OSRNG.posix as RNG
2  
3 def generate_nonce():
4     """Generate a random number used once."""
5     return RNG.new().read(AES.block_size)

I will note here that the python random module is completely unsuitable for cryptography (as it is completely deterministic). You shouldn’t use it for cryptographic code.

Symmetric ciphers are so-named because the key is shared across any entities. There are three key sizes for AES: 128-bit, 192-bit, and 256-bit, aka 16-byte, 24-byte, and 32-byte key sizes. Instead, we just need to generate 32 random bytes (and make sure we keep track of it) and use that as the key:

1 KEYSIZE = 32
2 
3 
4 def generate_key():
5     return RNG.new().read(KEY_SIZE)

We can use this key to encrypt and decrypt data. To encrypt, we need the initialisation vector (i.e. a nonce), the key, and the data. However, the IV isn’t a secret. When we encrypt, we’ll prepend the IV to our encrypted data and make that part of the output. We can (and should) generate a completely random IV for each new message.

 1 import Crypto.Cipher.AES as AES
 2 
 3 def encrypt(data, key):
 4     """
 5     Encrypt data using AES in CBC mode. The IV is prepended to the
 6     ciphertext.
 7     """
 8     data = pad_data(data)
 9     ivec = generate_nonce()
10     aes = AES.new(key, AES.MODE_CBC, ivec)
11     ctxt = aes.encrypt(data)
12     return ivec + ctxt
13 
14 
15 def decrypt(ciphertext, key):
16     """
17     Decrypt a ciphertext encrypted with AES in CBC mode; assumes the IV
18     has been prepended to the ciphertext.
19     """
20     if len(ciphertext) <= AES.block_size:
21         raise Exception("Invalid ciphertext.")
22     ivec = ciphertext[:AES.block_size]
23     ciphertext = ciphertext[AES.block_size:]
24     aes = AES.new(key, AES.MODE_CBC, ivec)
25     data = aes.decrypt(ciphertext)
26     return unpad_data(data)

However, this is only part of the equation for securing messages: AES only gives us confidentiality. Remember how we had a few other criteria? We still need to add integrity and authenticity to our process. Readers with some experience might immediately think of hashing algorithms, like MD5 (which should be avoided like the plague) and SHA. The problem with these is that they are malleable: it is easy to change a digest produced by one of these algorithms, and there is no indication it’s been changed. We need, a hash function that uses a key to generate the digest; the one we’ll use is called HMAC. We do not want the same key used to encrypt the message; we should have a new, freshly generated key that is the same size as the digest’s output size (although in many cases, this will be overkill).

In order to encrypt properly, then, we need to modify our code a bit. The first thing you need to know is that HMAC is based on a particular SHA function. Since we’re using AES-256, we’ll use SHA-384. We say our message tags are computed using HMAC-SHA-384. This produces a 48-byte digest. Let’s add a few new constants in, and update the KEYSIZE variable:

1 __aes_keylen = 32
2 __tag_keylen = 48
3 KEYSIZE = __aes_keylen + __tag_keylen

Now, let’s add message tagging in:

1 import Crypto.Hash.HMAC as HMAC
2 import Crypto.Hash.SHA384 as SHA384
3 
4 
5 def new_tag(ciphertext, key):
6     """Compute a new message tag using HMAC-SHA-384."""
7     return HMAC.new(key, msg=ciphertext, digestmod=SHA384).digest()

Here’s our updated encrypt function:

 1 def encrypt(data, key):
 2     """
 3     Encrypt data using AES in CBC mode. The IV is prepended to the
 4     ciphertext.
 5     """
 6     data = pad_data(data)
 7     ivec = generate_nonce()
 8     aes = AES.new(key[:__aes_keylen], AES.MODE_CBC, ivec)
 9     ctxt = aes.encrypt(data)
10     tag = new_tag(ivec + ctxt, key[__aes_keylen:]) 
11     return ivec + ctxt + tag

Decryption has a snag: what we want to do is check to see if the message tag matches what we think it should be. However, the Python == operator stops matching on the first character it finds that doesn’t match. This opens a verification based on the == operator to a timing attack. Without going into much detail, note that several cryptosystems have fallen prey to this exact attack; the keyczar system, for example, use the == operator and suffered an attack on the system. We’ll use the streql package (i.e. pip install streql) to perform a constant-time comparison of the tags.

 1 import streql
 2 
 3 
 4 def verify_tag(ciphertext, key):
 5     """Verify the tag on a ciphertext."""
 6     tag_start = len(ciphertext) - __taglen
 7     data = ciphertext[:tag_start]
 8     tag = ciphertext[tag_start:]
 9     actual_tag = new_tag(data, key)
10     return streql.equals(actual_tag, tag)

We’ll also change our decrypt function to return a tuple: the original message (or None on failure), and a boolean that will be True if the tag was authenticated and the message decrypted

 1 def decrypt(ciphertext, key):
 2     """
 3     Decrypt a ciphertext encrypted with AES in CBC mode; assumes the IV
 4     has been prepended to the ciphertext.
 5     """
 6     if len(ciphertext) <= AES.block_size:
 7         return None, False
 8     tag_start = len(ciphertext) - __TAG_LEN
 9     ivec = ciphertext[:AES.block_size]
10     data = ciphertext[AES.block_size:tag_start]
11     if not verify_tag(ciphertext, key[__AES_KEYLEN:]):
12         return None, False
13     aes = AES.new(key[:__AES_KEYLEN], AES.MODE_CBC, ivec)
14     data = aes.decrypt(data)
15     return unpad_data(data), True

We could also generate a key using a passphrase; to do so, you should use a key derivation algorithm, such as PBKDF2. A function to derive a key from a passphrase will also need to store the salt that goes with the passphrase. PBKDf2 takes three arguments: the passphrase, the salt, and the number of iterations to run through. The currently recommended minimum number of iterations in 16384; this is a sensible default for programs using PBKDF2.

What is a salt? A salt is a randomly generated value used to make sure the output of two runs of PBKDF2 are unique for the same passphrase. Generally, this should be a minimum of 16 bytes (128-bits).

Here are two functions to generate a random salt and generate a secret key from PBKDF2:

 1 import pbkdf2
 2 def generate_salt(salt_len):
 3     """Generate a salt for use with PBKDF2."""
 4     return RNG.new().read(salt_len)
 5 
 6 
 7 def password_key(passphrase, salt=None):
 8     """Generate a key from a passphrase. Returns the tuple (salt, key)."""
 9     if salt is None:
10         salt = generate_salt(16)
11     passkey = pbkdf2.PBKDF2(passphrase, salt, iterations=16384).read(KEYSIZE)
12     return salt, passkey

Keep in mind that the salt, while a public and non-secret value, must be present to recover the key. To generate a new key, pass None as the salt value, and a random salt will be generated. To recover the same key from the passphrase, the salt must be provided (and it must be the same salt generated when the passphrase key is generated). As an example, the salt could be provided as the first len(salt) bytes of the ciphertext.

That should cover the basics of block cipher encryption. We’ve gone over key generation, padding, and encryption / decryption. This code has been packaged up in the example source directory as secretkey.

ASCII-Armouring

I’m going to take a quick detour and talk about ASCII armouring. If you’ve played with the crypto functions above, you’ll notice they produce an annoying dump of binary data that can be a hassle to deal with. One common technique for making the data a little bit easier to deal with is to encode it with base64. There are a few ways to incorporate this into python: {Absolute Base64 Encoding}The easiest way is to just base64 encode everything in the encrypt function. Everything that goes into the decrypt function should be in base64 - if it’s not, the base64 module will throw an error: you could catch this and then try to decode it as binary data.

A Simple Header

A slightly more complex option, and the one I adopt in this article, is to use a \x00 as the first byte of the ciphertext for binary data, and to use \x41 (an ASCII “A”) for ASCII encoded data. This will increase the complexity of the encryption and decryption functions slightly. We’ll also pack the initialisation vector at the beginning of the file as well. Given now that the iv argument might be None in the decrypt function, I will have to rearrange the arguments a bit; for consistency, I will move it in both functions. My modified functions look like this now:

 1 def encrypt(data, key, armour=False):
 2     """
 3     Encrypt data using AES in CBC mode. The IV is prepended to the
 4     ciphertext.
 5     """
 6     data = pad_data(data)
 7     ivec = generate_nonce()
 8     aes = AES.new(key[:__AES_KEYLEN], AES.MODE_CBC, ivec)
 9     ctxt = aes.encrypt(data)
10     tag = new_tag(ivec+ctxt, key[__AES_KEYLEN:])
11     if armour:
12         return '\x41' + (ivec + ctxt + tag).encode('base64')
13     else:
14         return '\x00' + ivec + ctxt + tag
15       
16 def decrypt(ciphertext, key):
17     """
18     Decrypt a ciphertext encrypted with AES in CBC mode; assumes the IV
19     has been prepended to the ciphertext.
20     """
21     if ciphertext[0] == '\x41':
22         ciphertext = ciphertext[1:].decode('base64')
23     else:
24         ciphertext = ciphertext[1:]
25     if len(ciphertext) <= AES.block_size:
26         return None, False
27     tag_start = len(ciphertext) - __TAG_LEN
28     ivec = ciphertext[:AES.block_size]
29     data = ciphertext[AES.block_size:tag_start]
30     if not verify_tag(ciphertext, key[__AES_KEYLEN:]):
31         return None, False
32     aes = AES.new(key[:__AES_KEYLEN], AES.MODE_CBC, ivec)
33     data = aes.decrypt(data)
34     return unpad_data(data), True

A More Complex Container

There are more complex ways to do it (and you’ll see it with the public keys in the next section) that involve putting the base64 into a container of sorts that contains additional information about the key.

Public-key Cryptography

The original version of this document had examples of using RSA cryptography with Python. However, RSA should be avoided for modern secure systems due to concerns with advancements in the discrete logarithm problem. While I haven’t written Python in a while, I have done some research into packages for elliptic curve cryptography (ECC). The most promising one so far is PyElliptic, by Yann GUIBET.

Public key cryptography is a type of cryptography that simplifies the key exchange problem: there is no need for a secure channel to communicate keys over. Instead, each user generates a private key with an associated public key. The public key can be given out without any security risk. There is still the challenge of distributing and verifying public keys, but that is outside the scope of this document.

With elliptic curves, we have two types of operations that we generally want to accomplish:

  • Digital signatures are the public key equivalent of message authentication codes. Alice signs a document using her private key, and users verify the signature against her public key.
  • Encryption with elliptic curves is done by performing a key exchange. Alice uses a function called elliptic curve Diffie-Hellman (ECDH) to generate a shared key to encrypt messages to Bob.

There are three curves we generally use with elliptic curve cryptography:

  • the NIST P256 curve, which is equivalent to an AES-128 key (also known as secp256r1)
  • the NIST P384 curve, which is equivalent to an AES-192 key (also known as secp384r1)
  • the NIST P521 curve, which is equivalent to an AES-256 key (also known as secp521r1)

Alternatively, there is the Curve25519 curve, which can be used for key exchange, and the Ed25519 curve, which can be used for digital signatures.

Generating Keys

Generating new keys with PyElliptic is done with the ECC class. As we used AES-256 previously, we’ll use P521 here.

1 import pyelliptic
2 
3 
4 def generate_key():
5     return pyelliptic.ECC(curve='secp521r1')

Public and private keys can be exported (i.e. for storage) using the accessors (the examples shown are for Python 2).

1 >>> key = generate_key()
2 >>> priv = key.get_privkey()
3 >>> type(priv)
4 str
5 >>> pub = key.get_pubkey()
6 >>> type(pub)
7 str

The keys can be imported when instantiating a instance of the ECC class.

1 >>> pyelliptic.ECC(privkey=priv)
2 <pyelliptic.ecc.ECC instance at 0x39ba2d8>
3 >>> pyelliptic.ECC(pubkey=pub)
4 <pyelliptic.ecc.ECC instance at 0x39ad9e0>

Signing Messages

Normally when we do signatures, we compute the hash of the message and sign that. PyElliptic does this for us, using SHA-512. Signing messages is done with the private key and some message. The algorithm used by PyElliptic for signatures is called ECDSA.

1 def sign(key, msg):
2     """Sign a message with the ECDSA key."""
3     return key.sign(msg)

In order to verify a message, we need the public key for the signing key, the message, and the signature. We’ll expect a serialised public key and perform the import to a pyelliptic.ecc.ECC instance internally.

1 def verify(pub, msg, sig):
2     """Verify the signature on a message."""
3     return pyelliptic.ECC(curve='secp521r1', pubkey=pub).verify(sig, msg)

Encryption

Using elliptic curves, we encrypt using a function that generates a symmetric key using a public and private key pair. The function that we use, ECDH (elliptic curve Diffie-Hellman), works such that:

1 ECDH(alice_pub, bob_priv) == ECDH(bob_pub, alice_priv)

That is, ECDH with Alice’s private key and Bob’s public key returns the same shared key as ECDH with Bob’s private key and Alice’s public key.

With pyelliptic, the private key used must be an instance of pyelliptic.ecc.ECC; the public key must be in serialised form.

1 >>> type(priv)
2 <pyelliptic.ecc.ECC instance at 0x39ba2d8>
3 >>> type(pub)
4 str
5 >>> shared_key = priv.get_ecdh_key(pub)
6 >>> len(shared_key)
7 64

Our shared key is 64 bytes; this is enough for AES-256 and HMAC-SHA-256. What about HMAC-SHA-256? We could use a short key, or we could expand the last 32 bytes of the key using SHA-384 (which produces a 48-byte hash). Here’s a function to do that:

1 def shared_key(priv, pub):
2     """Generate a new shared encryption key from a keypair."""
3     shared_key = priv.get_ecdh_key(pub)
4     shared_key = shared_key[:32] + SHA384.new(shared_key[32:]).digest()
5     return shared_key

Ephemeral keys

For improved security, we should use ephemeral keys for encryption; that is, we generate a new elliptic curve key pair for each encryption operation. This works as long as we send the public key with the message. Let’s look at a sample EC encryption function. For this function, we need the public key of our recipient, and we’ll pack our key into the beginning of the function. This method of encryption is called the elliptic curve integrated encryption scheme, or ECIES.

 1 import secretkey
 2 import struct
 3 
 4 def encrypt(pub, msg):
 5     """
 6     Encrypt the message to the public key using ECIES. The public key
 7     should be a serialised public key.
 8     """
 9     ephemeral = generate_key()
10     key = shared_key(ephemeral, pub)
11     ephemeral_pub = struct.pack('>H', len(ephemeral.get_public_key()))
12     ephemeral += ephemeral.get_public_key()
13     return ephemeral_pub+secretkey.encrypt(msg, key)

Encryption packs the public key at the beginning, writing first a 16-bit unsigned integer containing the public key length and then appending the ephemeral public key and the ciphertext to this. Decryption needs to unpack the ephemeral public key (by reading the length and extracting that many bytes from the message) and then decrypting the message with the shared key.

1 def decrypt(pub, msg):
2     """
3     Decrypt an ECIES-encrypted message with the private key.
4     """
5     ephemeral_len = struct.unpack('>H', msg[:2])
6     ephemeral_pub = msg[2:2+ephemeral_len]
7     key = shared_key(priv, ephemeral_pub)
8     return secretkey.decrypt(msg[2+ephemeral_len:], key)

Key Exchange

So how does Bob know the key actually belongs to Alice? There are two main schools of thought regarding the authentication of key ownership: centralised and decentralised. TLS/SSL follow the centralised school: a root certificate1 authority (CA) signs intermediary CA keys, which then sign user keys. For example, if Bob runs Foo Widgets, LLC, he can generate an SSL keypair. From this, he generates a certificate signing request, and sends this to the CA. The CA, usually after taking some money and ostensibly actually verifying Bob’s identity2, then signs Bob’s certificate. Bob sets up his webserver to use his SSL certificate for all secure traffic, and Alice sees that the CA did in fact sign his certificate. This relies on trusted central authorities, like VeriSign3 Alice’s web browser would ship with a keystore of select trusted CA public keys (like VeriSigns) that she could use to verify signatures on the certificates from various sites. This system is called a public key infrastructure. The other school of thought is followed by PGP4 - the decentralised model.

In PGP, this is manifested as the Web of Trust5. For example, if Carol now wants to talk to Bob and gives Bob her public key, Bob can check to see if Carol’s key has been signed by anyone else. We’ll also say that Bob knows for a fact that Alice’s key belongs to Alice, and he trusts her6, and that Alice has signed Carol’s key. Bob sees Alice’s signature on Carol’s key and then can be reasonably sure that Carol is who she says it was. If we repeat the process with Dave, whose key was signed by Carol (whose key was signed by Alice), Bob might be able to be more certain that the key belongs to Dave, but maybe he doesn’t really trust Carol to properly verify identities. Bob can mark keys as having various trust levels, and from this a web of trust emerges: a picture of how well you can trust that a given key belongs to a given user.

The key distribution problem is not a quick and easy problem to solve; a lot of very smart people have spent a lot of time coming up with solutions to the problem. There are key exchange protocols (such as the Diffie-Hellman key exchange7 and IKE8 (which uses Diffie-Hellman) that provide alternatives to the web of trust and public key infrastructures.

Source Code Listings

secretkey.py

  1 # secretkey.py: secret-key cryptographic functions
  2 """
  3 Secret-key functions from chapter 1 of "A Working Introduction to
  4 Cryptography with Python".
  5 """
  6 
  7 import Crypto.Cipher.AES as AES
  8 import Crypto.Hash.HMAC as HMAC
  9 import Crypto.Hash.SHA384 as SHA384
 10 import Crypto.Random.OSRNG.posix as RNG
 11 import pbkdf2
 12 import streql
 13 
 14 
 15 __AES_KEYLEN = 32
 16 __TAG_KEYLEN = 48
 17 __TAG_LEN = __TAG_KEYLEN
 18 KEYSIZE = __AES_KEYLEN + __TAG_KEYLEN
 19 
 20 
 21 def pad_data(data):
 22     """pad_data pads out the data to an AES block length."""
 23     # return data if no padding is required
 24     if len(data) % 16 == 0:
 25         return data
 26 
 27     # subtract one byte that should be the 0x80
 28     # if 0 bytes of padding are required, it means only
 29     # a single \x80 is required.
 30 
 31     padding_required = 15 - (len(data) % 16)
 32 
 33     data = '%s\x80' % data
 34     data = '%s%s' % (data, '\x00' * padding_required)
 35 
 36     return data
 37 
 38 
 39 def unpad_data(data):
 40     """unpad_data removes padding from the data."""
 41     if not data:
 42         return data
 43 
 44     data = data.rstrip('\x00')
 45     if data[-1] == '\x80':
 46         return data[:-1]
 47     else:
 48         return data
 49 
 50 
 51 def generate_nonce():
 52     """Generate a random number used once."""
 53     return RNG.new().read(AES.block_size)
 54 
 55 
 56 def new_tag(ciphertext, key):
 57     """Compute a new message tag using HMAC-SHA-384."""
 58     return HMAC.new(key, msg=ciphertext, digestmod=SHA384).digest()
 59 
 60 
 61 def verify_tag(ciphertext, key):
 62     """Verify the tag on a ciphertext."""
 63     tag_start = len(ciphertext) - __TAG_LEN
 64     data = ciphertext[:tag_start]
 65     tag = ciphertext[tag_start:]
 66     actual_tag = new_tag(data, key)
 67     return streql.equals(actual_tag, tag)
 68 
 69 
 70 def decrypt(ciphertext, key):
 71     """
 72     Decrypt a ciphertext encrypted with AES in CBC mode; assumes the IV
 73     has been prepended to the ciphertext.
 74     """
 75     if len(ciphertext) <= AES.block_size:
 76         return None, False
 77     tag_start = len(ciphertext) - __TAG_LEN
 78     ivec = ciphertext[:AES.block_size]
 79     data = ciphertext[AES.block_size:tag_start]
 80     if not verify_tag(ciphertext, key[__AES_KEYLEN:]):
 81         return None, False
 82     aes = AES.new(key[:__AES_KEYLEN], AES.MODE_CBC, ivec)
 83     data = aes.decrypt(data)
 84     return unpad_data(data), True
 85 
 86 
 87 def encrypt(data, key):
 88     """
 89     Encrypt data using AES in CBC mode. The IV is prepended to the
 90     ciphertext.
 91     """
 92     data = pad_data(data)
 93     ivec = generate_nonce()
 94     aes = AES.new(key[:__AES_KEYLEN], AES.MODE_CBC, ivec)
 95     ctxt = aes.encrypt(data)
 96     tag = new_tag(ivec+ctxt, key[__AES_KEYLEN:])
 97     return ivec + ctxt + tag
 98 
 99 
100 def generate_salt(salt_len):
101     """Generate a salt for use with PBKDF2."""
102     return RNG.new().read(salt_len)
103 
104 
105 def password_key(passphrase, salt=None):
106     """Generate a key from a passphrase. Returns the tuple (salt, key)."""
107     if salt is None:
108         salt = generate_salt(16)
109     passkey = pbkdf2.PBKDF2(passphrase, salt, iterations=16384).read(KEYSIZE)
110     return salt, passkey

publickey.py

 1 # publickey.py: public key cryptographic functions
 2 """
 3 Secret-key functions from chapter 1 of "A Working Introduction to
 4 Cryptography with Python".
 5 """
 6 
 7 import Crypto.Hash.SHA384 as SHA384
 8 import pyelliptic
 9 import secretkey
10 import struct
11 
12 
13 __CURVE = 'secp521r1'
14 
15 
16 def generate_key():
17     """Generate a new elliptic curve keypair."""
18     return pyelliptic.ECC(curve=__CURVE)
19 
20 
21 def sign(priv, msg):
22     """Sign a message with the ECDSA key."""
23     return priv.sign(msg)
24 
25 
26 def verify(pub, msg, sig):
27     """
28     Verify the public key's signature on the message. pub should
29     be a serialised public key.
30     """
31     return pyelliptic.ECC(curve='secp521r1', pubkey=pub).verify(sig, msg)
32 
33 
34 def shared_key(priv, pub):
35     """Generate a new shared encryption key from a keypair."""
36     key = priv.get_ecdh_key(pub)
37     key = key[:32] + SHA384.new(key[32:]).digest()
38     return key
39 
40 
41 def encrypt(pub, msg):
42     """
43     Encrypt the message to the public key using ECIES. The public key
44     should be a serialised public key.
45     """
46     ephemeral = generate_key()
47     key = shared_key(ephemeral, pub)
48     ephemeral_pub = struct.pack('>H', len(ephemeral.get_pubkey()))
49     ephemeral_pub += ephemeral.get_pubkey()
50     return ephemeral_pub+secretkey.encrypt(msg, key)
51 
52 
53 def decrypt(priv, msg):
54     """
55     Decrypt an ECIES-encrypted message with the private key.
56     """
57     ephemeral_len = struct.unpack('>H', msg[:2])[0]
58     ephemeral_pub = msg[2:2+ephemeral_len]
59     key = shared_key(priv, ephemeral_pub)
60     return secretkey.decrypt(msg[2+ephemeral_len:], key)
  1. A certificate is a public key encoded with X.509 and which can have additional informational attributes attached, such as organisation name and country.
  2. The extent to which this actually happens varies widely based on the different CAs.
  3. There is some question as to whether VeriSign can actually be trusted, but that is another discussion for another day…
  4. and GnuPG
  5. http://www.rubin.ch/pgp/weboftrust.en.html
  6. It is quite often important to distinguish between I know this key belongs to that user and I trust that user. This is especially important with key signatures - if Bob cannot trust Alice to properly check identities, she might sign a key for an identity she hasn’t checked.
  7. http://is.gd/Tr0zLP
  8. https://secure.wikimedia.org/wikipedia/en/wiki/Internet_Key_Exchange