Sub Rosa
Lab setup
First, make sure you have completed the initial setup.
If you are part of a course
-
Open Terminal. Run the update command to make sure you have the latest code.
$ mwc update
-
Move to this lab's directory.
$ cd ~/Desktop/making_with_code/ny-csdf/unit2/lab_subrosa
-
Enter this lab's shell environment.
$ poetry shell
If you are working on your own
-
Move to your MWC directory.
$ cd ~/Desktop/making_with_code
-
Get a copy of this lab's materials.
git clone https://git.makingwithcode.org/mwc/lab_subrosa.git
In the previous lab, we explored how information can be encrypted to keep it secret. But what if you want to send encrypted messages to someone else? If you both know the secret number or word, then you can just encrypt your message, send it to them, and they can decrypt it. But if you're worried about your messages getting intercepted, how can you share the secret with them in the first place?
Sure, if you and your friend meet up for coffee once a week you could just agree on a secret in person. But this won't work when people are far away from each other, who don't have a way to securely share secrets, or who don't even know each other yet.
In this lab we will explore a brilliant solution to this problem: public key encryption. In public key encryption, you generate two paired keys at the same. One is public and is shared with the world. (For example, you might publish your public key on your website.) The private key is kept secret. Either key can be used to encrypt a message, and only the other key can be used to decrypt the message.
With public key encryption, if you want to send someone an encrypted message you can encrypt it using their public key. Then this message can only be decrpyted by someone with the private key. If you want to send someone a message and prove it's from you, you can encrypt it using your private key. Anyone can then use your public key to decrypt it, and they can be sure that the sender had the private key. The math behind this is beautiful, but we're not going to get into it in this lab.
Public key encryption is the main form of encryption used in computer systems today.
Public and private keys
The encryption
module provides a real implementation of public key
encryption using the PrivateKey
and PublicKey
classes.
PrivateKey
has the following methods:
PrivateKey.generate()
Randomly generates a new private key. This is a class method, called using thePrivateKey
class, not an instance.PrivateKey.load(filename)
Loads a key from a saved key file (or a string representation of the key). This is a class method.PrivateKey.get_public_key()
Returns the correspondingPublicKey
.PrivateKey.save(filename)
Saves this key to a file.PrivateKey.decrypt(ciphertext)
Decrypts a message encrypted with the correspondingPublicKey
.PrivateKey.sign(message)
.
PublicKey
has the following methods:
PublicKey.load(filename)
Loads a key from a saved key file (or a string representation of the key). This is a class method.PublicKey.save(filename)
Saves this key to a file.PublicKey.encrypt(message)
Encrypts the message. Only the correspondingPrivateKey
can decrypt the message.PublicKey.verify_signature(message, signed_message)
Verifies thatsigned_message
can be decrypted intomessage
, which proves thatsigned_message
was encrypted with the correspondingPrivateKey
.
💻 Let's try them out in Python's interactive mode. First, practice creating private and public keys. As their names imply, the private key is kept secret and the public key is shared with everyone.
$ python -i encryption.py
>>> private = PrivateKey.generate()
>>> public = private.get_public_key()
>>> print(private)
...
>>> print(public)
...
>>> private.save('test_key.pem')
>>> PrivateKey.load('test_key.pem')
💻 Next, practice encrypting and decrypting messages. If you want to send someone a secret message, you can encrypt it using their public key. The result can only be decrypted using the matching private key. People publish their private keys on their websites, in email footers, or using key servers.
>>> message = "Would you like to eat lunch together?"
>>> secret = public.encrypt(message)
>>> secret
'KDNq8FVjnm9k3NOpiNs5JmzIaIwaTz6QCHz/xtCbPeFCmTNFDBBeURMFwomXNvrjkhIT...'
>>> private.decrypt(secret)
'Would you like to eat lunch together?'
>>> private2 = PrivateKey.generate()
>>> private2.decrypt(secret)
ValueError: Decryption failed
💻
Finally, practice signing a message with your private key.
Signing a message is a way of proving you have the private key, while keeping the
key itself a secret. To create a signature, choose any message (e.g. "It's me!"
)
and encrypt it with the private key. Then if you send someone the message and its
encrypted ciphertext, they can decrypt the message with the public key, and make sure
that the decrypted message matches the original message.
>>> signature = private.sign("It's me!")
>>> signature
'CwxoTLCRfzG7l6FiTUe1hK0htZbXdEs+jOOG86xsw3Ss1emlhoivdAPWH48pT0E70YMU...'
>>> public.verify_signature("It's me!", signature)
>>> public2 = private2.get_public_key()
>>> public2.verify_signature("It's me!", signature)
cryptography.exceptions.InvalidSignature
Sub Rosa server
This lab's code contains a client and a server for an encrypted chat system
called Sub Rosa. The
server is a banjo
app, which you can run locally, and which is also
running live at https://subrosa.makingwithcode.org
. Before we can use the
server, we need to generate a pair of keys.
💻 Generate a pair of keys and save them into files.
$ python -i encryption.py
>>> private = PrivateKey.generate()
>>> public = private.get_public_key()
>>> private.save("subrosa_private_key.pem")
>>> public.save("subrosa_public_key.pem")
💻
Now create an account. Unfortunately, the username funkychicken5
is already taken, so you'll have to use something else.
(The @
is a shortcut
which means "read in the contents of this file.")
$ http post https://subrosa.makingwithcode.org/users/new name=funkychicken5 public_key=@subrosa_public_key.pem
💻 Check your messages--there's already one from the Sub Rosa administrator! The message is encrypted, so you'll need to use your private key to decrypt it.
$ http get https://subrosa.makingwithcode.org/messages name=funkychicken5
{
"messages": [
{
"ciphertext": "MXGwPnazj0JK+tE6Qmi62FXQVTMAmlePPe1mtGE6Qb...",
"recipient": "funkychicken5",
"sender": "subrosa_admin"
}
]
}
If you want to send a message to someone else, you'll need to look up their public key so you can use it to encrypt the message. Sub Rosa uses end-to-end encryption, which means you need to encrypt messages before you send them to the server, and the recipient will decrypt them on their own computer. The plaintext message is never sent over the Internet, and is never stored on the server.
💻 Look up a user's public key.
$ http get https://subrosa.makingwithcode.org/users name=funkychicken5
{
"name": "funkychicken5",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQ..."
}
Finally, you can send messages to other users, but this is a bit tricky to do from the command line as you need to generate a signature (to prove your own identity). We will implement message-sending using the client program in the next section.
Sub Rosa client
A partially-working SubRosa client program is provided.
💻 Look at the client program's help message. A username is required; you can optionally give a key file or a server URL.
$ python client/user_interface.py --help
usage: user_interface.py [-h] [-k KEY] [-s SERVER_URL] username
positional arguments:
username Existing or new username.
options:
-h, --help show this help message and exit
-k KEY, --key KEY Private key file. Will be created if it does not exist.
-s SERVER_URL, --server-url SERVER_URL
Server URL. For example, use 127.0.0.1:5000 for a local server
💻 Use the client program to read your messages. Unfortunately, the client isn't quite finished--the encrypted messages we fetch from the server aren't being decrypted. Also, sending messages doesn't work.
$ python client/user_interface.py funkychicken5
Welcome to SubRosa.
What would you like to do?
1. See messages
2. Send a message
> 1
--------------------------------------------------------------------------------
0. From subrosa_admin:
MXGwPnazj0JK+tE6Qmi62FXQVTMAmlePPe1mtGE6QbcNXT41MPvjzxZbUCgAdrNnrk9YD...
================================================================================
What would you like to do?
1. See messages
2. Send a message
> 2
Username of recipient: funkychicken5
Message: Hello.
--------------------------------------------------------------------------------
Sorry, couldn't send a message to funkychicken5; this method isn't implemented yet.
================================================================================
Your task:
- Update the
show_messages
method ofclient.user_interface.SubRosaUI
so that it displays the decrypted messages. - Update the
send_message
method ofclient.user_interface.SubRosaUI
so that messages are actually sent. There are a few steps involved--you need to encrypt the message so only the recipient can read it, and you need to generate a signature proving that you really are the sender (or at least that you have the sender's private key).- Ask the user for the recipient's username. Get the recipient's public key from the server. (This is already done.)
- Ask the suer for the message content. (This is already done.)
- Use the recipient's public key to encrypt the message into ciphertext.
- Get a string representing the current time, using
get_current_time()
. - Use the user's private key to create a signature from the current time string.
- call
self.api.send_message(sender, recipient, ciphertext, time_sent, time_sent_signature)
.