This repository provides some sample code for the Shared Project for Modern Cryptography and Security Management & Compliance. The project requires git, Python 3, and MongoDB. The following sections briefly explain how to setup the project on your local machine.
Create a GitHub account. Download and install
git. We will use git
to manage our source
code.
Verify that git
is installed correctly:
git --version
Fork this repository and clone your forked repository to your local machine:
git clone https://github.com/YOUR_GITHUB_USERNAME/cyber-students.git
Create a Python 3 virtual environment:
python -m venv project-venv
Activate the virtual environment:
:: ... on Windows:
.\project-venv\Scripts\activate
# ... on macOS/*nix:
source project-venv/bin/activate
Install the required packages:
cd cyber-students
pip install -r requirements.txt
Download, install and start MongoDB Community Edition. We will use MongoDB as our database.
Download and install MongoDB Shell. Open a MongoDB shell:
mongosh
Create two databases with a collection named users
in each:
use cyberStudents;
db.createCollection('users');
use cyberStudentsTest;
db.createCollection('users');
The first database will store our 'real' data. The second database will be used by our tests.
Download and install curl. curl
is also shipped
by Microsoft as part of Windows 10 and 11. curl
is a command-line
tool for interacting with web servers (and other protocols).
Verify that curl
is installed correctly:
curl --version
The server contains functionality for:
- registering new users (
api/handlers/registration.py
) - logging in (
api/handlers/login.py
) - logging out (
api/handlers/logout.py
) - displaying profile (
api/handlers/user.py
)
To start the server:
python run_server.py
The server is available on port 4000 at
http://localhost:4000/students/api. However, it is not possible to
use all of the functionality offered by the server directly using a
browser. Instead we will use curl
to interact with the server.
To register a new user:
curl -X POST http://localhost:4000/students/api/registration -d "{\"email\": \"[email protected]\", \"password\": \"strongpassword\", \"displayName\": \"Nigel Douglas\", \"fullName\": \"Nigel Douglas\", \"phoneNumber\": \"02537641\", \"disability\": \"leukemia\"}"
If the registration is successful, it will confirm the email address and the display name of the newly registered user:
{"email": "[email protected]", "displayName": "Foo Bar"}
If the registration is unsuccessful, for example, if you try to register the same user twice, it will return an error message:
{"message": "A user with the given email address already exists!"}
To login:
curl -X POST http://localhost:4000/students/api/login -d "{\"email\": \"[email protected]\", \"password\": \"pass\"}"
If the login is successful, it will return a token and expiration timestamp:
{"token": "d4a5d8b20fe143b7b92e4fba92d409be", "expiresIn": 1648559677.0}
A token expires and is intended to be short-lived. A token expires two hours after login, after a logout, or if there is another login from the same user, generating a new token.
If the login is unsuccessful, for example, if you provide an incorrect password, it will return an error message:
{"message": "The email address and password are invalid!"}
To display a user's profile you need to a token that has not expired. Then you can use:
curl -H "X-TOKEN: d4a5d8b20fe143b7b92e4fba92d409be" http://localhost:4000/students/api/user
Note that this API call does not require the -X POST
flag.
If successful, it will return the email address and the display name for the user:
{"email": "[email protected]", "displayName": "Foo Bar"}
To logout, you also need a token that has not expired. Then you can use:
curl -X POST -H "X-TOKEN: d4a5d8b20fe143b7b92e4fba92d409be" http://localhost:4000/students/api/logout
You can run the automated tests using:
python run_test.py
This command runs a number of automated tests in the tests
folder.
The tests read and store data in the cyberStudentsTest
database
only. They perform tests such as registering new users
(tests/registration.py
), logging in (tests/login.py
), and logging
out (tests/logout.py
).
The project also includes a program called run_hacker.py
. You can
run it using:
python run_hacker.py list
It displays all information stored in the MongoDB database. It produces output similar to the following:
There are 1 registered users:
{'_id': ObjectId('6242d9c34536b3a16b49aa6b'), 'email': '[email protected]', 'password': 'pass', 'displayName': 'Foo Bar'}
As you can see, all of the information is stored in the clear; there is no encryption or password hashing. If a hacker was to compromise the database, they could easily run a similar program to retrieve all of the users personal information and passwords.
You can see the original 4 registrations did not enforce encryption.
The latest test has a new field called fullName
. The full name and password are encrypted:
Fernet guarantees that a message encrypted using it cannot be manipulated or read without the key.
Fernet is an implementation of symmetric (also known as “secret key”) authenticated cryptography.
Fernet also has support for implementing key rotation via MultiFernet .
Before and after the encryption - as seen in MongoDB Compass
git remote -v
shows me which repo I'm currently connected to.
git remote set-url origin
sets the desired Github account to push changes to.
git push -f
forcefully pushes the changes to the Github branch - even if there is conflict
git status
ensures I'm on the master branch. I don't want to commit to the wrong branch
git pull
checks that everything is already up-to-date
git diff
shows the differences between the file locally and in github
git add
simply adds the files to the approval process
git commit
commits the changes to GitHub -m
flag is to add a description
git push
finally pushes the changes up to my Github master
git remote -v
git remote set-url origin [email protected]:nigeldouglas-itcarlow/cyber-students.git
git push - f
git status
git pull
git diff requirements.txt
git add requirements.txt
git commit -m "adding password hasher package"
git push
I'm able to parse the full_name
field to JSON:
curl -X POST http://localhost:4000/students/api/registration -d "{\"email\": \"[email protected]\", \"password\": \"happier\", \"displayName\": \"happier\", \"fullName\": \"Mr. Happy\"}"
{"email": "[email protected]", "displayName": "happier", "fullName": "Mr. Happy"}%
However, I'm unable to parse the phone_number
field to JSON:
curl -X POST http://localhost:4000/students/api/registration -d "{\"email\": \"[email protected]\", \"password\": \"happier\", \"displayName\": \"happier\", \"fullName\": \"Mr. Happy\", \phoneNumber\": \"02136541\"}"
Eventually loaded the Disabilities
and Phone Numbers
The issue was not with the fields I created, it was with the run_server.py
script
Between tests, I need to kill the process manually, and then restart it to see the changes.
ps aux | grep run
kill -9 55879
python run_server.py
Ensuring the character length has to exceed 6 characters
:
if len(password) < 6:
self.send_error(400, message='The password must be at least 6 characters long!')
return
import re
password = input("Create a password: ")
password_pattern = re.compile(r'^(?=.*[A-Z])(?=.*[a-z])(?=.*\d)[A-Za-z\d]{6,}$')
while not password_pattern.match(password):
print("Your password must contain at least one uppercase letter, one lowercase letter, and one digit, and be at least 6 characters long.")
password = input("Create a password: ")
print("Password set successfully!")
Using regular expressions, (password_pattern
) to ensure that the password contains at least:
- one uppercase letter
- one lowercase letter
- one digit
- at least 14 characters long.
If the user's input does not match this pattern, the program will prompt them to create a new password until the requirements are met.
It's not okay to hardcode your cryptography keys
in the source code
Instead, I can load it from a file or to use environmental variables (recommended
)
Loading the key from a file is a more common approach to avoid hardcoding sensitive data in the code.
I created a new file named secret.env
in the same directory as my Python script.
The load_dotenv
function loads environment variables from the .env file, and os.getenv('CRYPTO_KEY')
retrieves the value of the CRYPTO_KEY
environment variable set in the .env file. The .encode()
method converts the string key to bytes, which is needed for the Fernet module to work.
I made sure to install the python-dotenv
package by running pip install python-dotenv
in the terminal.
There was a long learning curve when working with git
However, I was able to add /modify files in the end:
When I felt the project was in a working state, I merged from nigel-branch
to the master
branch