Crypto Tutorial

This tutorial walks you through some basic crypto operations that you’ll need for the secure layer.

For most of this, I’ll be suing the Python Cryptography Toolkit (PyCrypto). This module is imported as “Crypto”, although you usually import one or more submodules. Please make sure you have the right package installed. There are other packages named “crypto” that are not the right one.

I’ll also be using a few Playground specific crypto modules for the x509 stuff.


AES ENCRYPTION

To use AES encryption in the Crypto module, you need to import the submodule Cipher. So, typically, you call import Crypto.Cipher or perhaps from Crypto.Cipher import AES

Using the AES module, you can create a new AES object using the “new” method. It requires at least a key, but generally an IV and mode parameter. Here’s an example for AES CBC:

from Crypto.Cipher import AES

zeroKey = "\x00"*16 # 16 bytes of 0
zeroIv = "\x00"*16
aesCrypto = AES.new(zeroKey, IV=zeroIv, mode=AES.MODE_CBC)

Note that the block size for the AES is determined by the key size you pass to new. Passing in a 16 byte key selects AES-128. The only legitimate lengths are 16, 24, and 32 (for AES-128, AES-256, and AES-512 respectively).

The other modes include ‘MODE_CBC’, ‘MODE_CFB’, ‘MODE_CTR’, ‘MODE_ECB’, ‘MODE_OFB’, ‘MODE_OPENPGP’, ‘MODE_PGP’. The PETF is talking about making the bulk encryption counter mode, so you’d probably be more interested in MODE_CTR.

The counter mode changes the API a little. It’s ridiculous, but they didn’t want to just have the counter mode set by IV. SO WHEN USING COUNTER MODE, IV IS IGNORED. Instead, they want you to set “counter” value to a callable function.

The Crypto library provides some specialized, high-speed counter objects that you should use instead of your own. The Crypto.Util.Counter object is a “callable” (it acts like a function) and can be passed to the AES contrustor for counter mode. Let’s look at this object by itself, and then how you plug it into AES.

from Crypto.Util import Counter
ctr = Counter.new(128)
print ctr() 
# this will print out '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01'
print ctr()
# this will print out '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02'

The first argument to the Counter’s constructor is the number of bits for the counter. It must be a multiple of 8 and, for the AES module, it needs to be the same as block size (128, 256, or 512). By default, the Counter starts at 1. Obviously, you’ll want to change this. Fortunately, it accepts a parameter called “initial_value”. Unfortunately, it expects an int, which doesn’t work well with IV’s (I strongly suggest you still think of the starting counter mode as an IV).

However, python makes it easy to construct an arbitrary sized “integer” (it can be any number of bytes). Suppose we have a 16 byte IV in a variable called, unimaginatively, “IV”. Here is how you convert it to an int, and use it to construct the counter.

from Crypto.Util import Counter
IV = # 16 bytes of IV
IV_asInt = int(IV.encode('hex'), 16)
IV_asCtr = Counter.new(128, initial_value=IV_asInt)

Once you have the counter, you can setup your AES_CTR object thusly:

from Crypto.Util import Counter
from Crypto.Cipher import AES
zeroKey = "\x00"*16 # 16 bytes of 0
zeroIv = "\x00"*16
IV_asCtr = Counter.new(128, initial_value=int(zeroIv.encode('hex'),16))
aesCrypto = AES.new(zeroKey, counter=IV_asCtr, mode=AES.MODE_CTR)

Now that you have your AES object, you can encrypt and decrypt with it:

ciphertext = aesCrypto.encrypt(plaintext)

PLEASE NOTE that the aes object maintains STATE. And you want it to. So, if you call encrypt twice, it will update the counter (IV) with each call.

The corollary is that you must have a separate object for decryption (because otherwise the state would not be in the same place). You will get the wrong plaintext if you do this:

aesCrypto = AES.new(zeroKey, counter=IV_asCtr, mode=AES.MODE_CTR)
plaintext = aesCrypto.decrypt(aesCrypto.encrypt(plaintext))

but not if you do this:

IV_asCtr1 = Counter.new(128, initial_value=int(zeroIv.encode('hex'),16))
IV_asCtr2 = Counter.new(128, initial_value=int(zeroIv.encode('hex'),16))
aesEncrypter = AES.new(zeroKey, counter=IV_asCtr1, mode=AES.MODE_CTR)
aesDecrypter = AES.new(zeroKey, counter=IV_asCtr2, mode=AES.MODE_CTR)

# I used two separate counters, even though they start at the same value
# Why? Because it also maintains state! Please be careful! don't reuse counters!

aesDecrypter.decrypt(aesEncrypter.encrypt(plaintext))

It should be obvious that, for your decrypter to work, it’s initial counter (IV) has to be the same as the one used for the encrypter.

Of course, because you know your crypto so well, you know that for counter mode, encrypt and decrypt are interchangeable.


HMAC

HMAC in the PyCrypto library is much more straightforward. Here’s an example.

from Crypto.Hash import HMAC, SHA256
hm1 = HMAC.new(key, digestmod=SHA256)
hm1.update(data)
hm1.digest()

X509

For these operations, you will need both some of the packages from Crypto as well as X509Certificate from playground.crypto. For these operations, we’re going to do both some signatures as well as encryption/decryption. Let’s talk about signing and verifying first.

For signing, you will obviously need the private key, where as for verification you only need the certificate (with the public key in it). Let’s assume that we have the private key in a file name “privateKey.pem”. We first need to create an RSA PKCS1 v1.5 signer. Fortunately, Crypto has exactly that class as shown below:

from Crypto.Signature import PKCS1_v1_5
from Crypto.PublicKey import RSA

with open("privateKey.pem") as f:
  rawKey = f.read()
rsaKey = RSA.importKey(rawKey)
rsaSigner = PKCS1_v1_5.new(key)

To sign data, we do have to hash first, with an algorithm of our choice. I believe all the class certificates are SHA256, yes?

hasher = SHA256.new()
hasher.update(data)
signatureBytes = rsaSigner.sign(hasher)

Verification requires that we first extract the public key from the certificate. This functionality was not in Crypto, so I provided the X509Certificate class. Suppose that we have a cert in the file “cert.pem” To verify a signature:

from playground.crypto import X509Certificate
with open("cert.pem") as f:
  certBytes = f.read()
cert = X509Certificate.loadPEM(certBytes)
peerPublicKeyBlob = cert.getPublicKeyBlob()
peerPublicKey = RSA.importKey(peerPublicKeyBlob)
rsaVerifier = PKCS1_v1_5.new(peerPublicKey)
hasher = SHA256.new()
hasher.update(data)
result = rsaVerifier.verify(hasher, signature)

Please note that, to sign or verify a certificate, you need the data that is signed and/or verified (e.g., the contents of the cert MINUS the signature portion). Moreover, you’ll also need to extract the signature from the cert. Conveniently, my X509 class provides such methods. So, suppose we want to verify the cert.

data = cert.getPemEncodedCertWithoutSignatureBlob()
hasher.update(data)
result = rsaVerifier.verify(hasher, cert.getSignatureBlob())

But what about verifying a chain? You’ll notice that if you were to follow the above code exactly, it would only work for self-signed certificates (e.g., the code above checks the signature on the cert using the public key in the cert). What if you want to see if the cert is signed by root?

Certificates encode within themselves who issued them. This can be extracted from the X509Certificate class using the “getIssuer” method. the data from this method should be the same data returned from the “getSubject” method of the signing cert. So, suppose that we have the root cert, and a cert that purports to be signed by root in two X509Certificate objects rootCert and cert.

if (cert.getIssuer() != rootCert.getSubject()):
  return False
# now let's check the signature
rootPkBytes = rootCert.getPublicKeyBlob()
# use rootPkBytes to get a verifiying RSA key
# not shown here. Use the code above
bytesToVerify = cert.getPemEncodedCertWithoutSignatureBlob()
hasher.update(bytesToVerify)
if not rootVerifier.verify(hasher, cert.getSignatureBlob()):
  return False

Please note that the issuer and subject are not strings. They are dictionaries mapping multiple fields to strings. To get the CommonName, for example, you would do this:

print cert.getSubject()["commonName"]

Once you have a “trusted” certificate, you may choose to use the public key to encrypt a message to the certificate’s owner. To do this, you need to use the RSA encryption method, ALONG with a secure padding. I’m not going to get into why the padding is necessary for security. If you are curious, read the internet about PKCS1 OAEP.

So, first, you need to extract the public key from the cert, turn it into an RSA key object, and then use the proper encryption/padding scheme. Here is an example:

from Crypto.Cipher.PKCS1_OAEP import PKCS1OAEP_Cipher
peerPublicKeyBytes = cert.getPublicKeyBlob()
peerPublicKey = RSA.importKey(peerPublicKeyBlob)
peerRsaEncrypter = PKCS1OAEP_Cipher(peerPublicKey, None, None, None)

Now, using the rsa encrypter, you can encipher a message such that only the owner of the cert (and the corresponding public key) can read. You encrypt using “peerRsaEncrypter.encrypt(data)”

But how do you decrypt? This is asymmetric crypto after all. So you create a decrypter from the private key.

# Load 'keyBytes' from the key file (pem encoded)
key = RSA.importKey(keyBytes)
rsaSigner = PKCS1_v1_5.new(key)
rsaDecrypter = PKCS1OAEP_Cipher(key, None, None, None)

Notice that the same private key used for signing is the same private key used for decrypting. I didn’t show it above, but the same public key used for verifying is the same public key used for encrypting. I generally create my private keys without passwords, which is probably not a great security idea, but which just makes things easier for playground. However, if your private keys are password protected, you can provide a password in the “importKey” function by setting the argument passphrase.

privateKey = RSA.importKey(keyBytes, passphrase="hax0rzRulz")

Once you have this encrypted channel set up, you can send a 256-bit AES key and 256-bit IV. These can be used for bulk encrypting/decrypting the data sent back and forth between the two peers.

Hopefully this gives you what you need to get started.

Leave a Reply

Your email address will not be published. Required fields are marked *