Lab: Command-Line Tools and Exceptions
Due: Wednesday, October 7 at 11:59pm Worth: 8 points
Install and clean up a command-line tool, then write a password cracker that recovers from failed guesses. Cowsay earns 4 points and the cracker earns 4 points. Both live in one repository and can be completed in either order. GitHub Actions are practice; Gradescope runs the graded checks itself.
Start
Open the starter repository and select Use this template to create lab-cli-tools under your own account. Clone your copy and open it in VS Code. See course setup for the commit, sync, and submission workflow. A starter ZIP is also available: unzip it, open the inner lab-cli-tools folder, then use Initialize Repository and Publish to GitHub in VS Code. Use this one repository for every part below; do not create separate repositories for the parts.
Part A: Package and Clean Up Cowsay
The starter’s [tool.setuptools] section lists packages = ["cowsay"]. Keep it: the decrypted-files folder you create in Part B is data, not another Python package.
pip
The repo contains a script that draws a cow saying whatever you tell it:
$ python3 cowsay/__main__.py 'mooooo moooo'
______________
| mooooo moooo |
--------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||Typing python3 cowsay/__main__.py every time you want the cow to talk gets old fast. Python’s package installer, pip, can install the script into a shorter, easier-to-use form. Try installing this project from inside its own folder:
$ pip3 install .Depending on your computer, you might get an error ending with error: externally-managed-environment. Recent versions of Python restrict system-wide package installation to avoid conflicting dependencies, sometimes called dependency hell.
The fix is a virtual environment, or venv: a project-specific place to install libraries so every project keeps its own set. Create one for this project, then activate it:
$ python3 -m venv venv
$ source venv/bin/activateOn Windows PowerShell, activate with venv\Scripts\Activate.ps1 instead. If activation is blocked, use a Command Prompt terminal and venv\Scripts\activate.bat.
Your prompt changes to include (venv), and now the install runs cleanly:
$ pip3 install .You create the venv once, but you have to run the source venv/bin/activate line again every time you reopen the project in VS Code.
With the project installed, the cowsay command works on its own, from any folder:
$ cowsay 'moo'
_____
| moo |
-----
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||The same trick installs libraries other people wrote. Last week you installed yt-dlp by cloning it and running the script by hand; far more often, you install a package straight from its name with pip:
$ pip3 install yt-dlpGive pip a package name, and it downloads and installs the package. yt-dlp then becomes a command you can run anywhere.
A small joke worth knowing: pip stands for “Pip Installs Packages”, an acronym that contains itself. These recursive acronyms are a running gag in programming, and GNU (“GNU’s Not Unix”) is the most famous one.
Your task. The command_line action is failing because it runs the cowsay command without installing it first. Follow the instructions inside .github/workflows/command_line.yaml so that the action passes.
PEP8 and flake8
Python Enhancement Proposals (PEPs) are how new features and conventions get added to Python. The most famous is PEP 8, which lays out how to format Python so it reads consistently. For instance, PEP 8 wants spaces around operators, so this:
x=1+2should be written as:
x = 1 + 2Both run identically; the second is just easier to read. Programmers enforce these conventions with tools called linters. (The name comes from lint on clothing: code that ignores PEP 8 still works, it just looks a bit fuzzy.) The best-known Python linter is flake8, installed like any other package:
$ pip3 install flake8Point it at the cowsay code and it lists every place the formatting strays from PEP 8:
$ flake8 --isolated cowsay
cowsay/__main__.py:8:19: E201 whitespace after '('
cowsay/__main__.py:8:49: E202 whitespace before ')'
cowsay/__main__.py:9:1: W293 blank line contains whitespace
cowsay/__main__.py:12:23: E225 missing whitespace around operator
cowsay/__main__.py:13:1: W293 blank line contains whitespace
cowsay/__main__.py:14:23: E225 missing whitespace around operatorEach line gives the file, line, column, and rule for one violation.
Your task. The flake8 action runs this linter and fails if it reports anything. Edit cowsay/__main__.py until flake8 returns no errors and the action passes.
Part B: Recover from Failed Password Guesses
Opening Zip Files in Python
Python opens password-protected zip files with the built-in zipfile module. Start a new file with this:
from zipfile import ZipFile
with ZipFile('guido_secrets.zip') as zf:
password = b'BFDL'
zf.extractall(pwd=password)After the code runs, a new file appears at the relative path guido_secrets/secrets.txt, inside the guido_secrets folder. The file holds a poem, The Zen of Python. Open it in VS Code.
The password is a bytes object (b'BFDL'), not an ordinary str. Zip passwords are raw bytes, and while they usually spell out ASCII text, they do not have to. Change b'BFDL' to the plain string 'BFDL' and you get a TypeError because pwd requires bytes. You can convert a string to bytes with .encode, so 'BFDL'.encode('ascii') gives the same value as b'BFDL'. Change the password to anything other than BFDL and run it again: you get an error (RuntimeError, BadZipFile, or zlib.error, depending on your system), and any files it does create will hold garbage rather than the real contents.
Zip Bombs
Some zip files are zip bombs, which expand to consume excessive storage when decompressed. Antivirus software routinely opens zip files to scan inside them, and opening the wrong one can take a machine down: decompressing it can fill the entire hard drive.
Zip bombs can contain huge amounts of repeated data or recursively nested archives. They are not included in this starter. Work only with the two small practice archives, guido_secrets.zip and whitehouse_secrets.zip.
The Scenario
For this lab, pretend it is 2015 and you are an analyst at the GRU, the Russian military-intelligence agency. One of your agents has risked their life to bring you whitehouse_secrets.zip, stolen from a White House IT worker and said to hold details of the upcoming US presidential election. Your job is to open it, but the file is encrypted and you do not have the password. You do have leads.
In July 2015, the affair-oriented dating site Ashley Madison was breached and its entire user database leaked onto the internet.
The badges on that homepage promise that the data is safe and encrypted. The leak became a prominent example of hacktivism. The White House IT worker who made the zip was an Ashley Madison user. Like most people, they reused a single password everywhere, so one of the leaked passwords will open the file.
The scenario draws on a Defense One report that 45 White House staffers and more than 10,000 military personnel had Ashley Madison accounts. The Associated Press confirmed a White House IT staffer among them.
Your Tasks
The SecLists repository collects security datasets, including the Ashley Madison passwords at
Passwords/Leaked-Databases/Ashley-Madison.txt. Download that file.Write a program
password_cracker.pythat finds the zip file’s password. It should:Hint. Use a
try/exceptto tell whether the zip opened: try each password, catch a failure, and continue to the next password. A zip password must bebytes, so call.encode()on each one. A line read from a file includes its trailing newline, so call.strip()before trying it.Hint. There are a lot of passwords, and trying them all takes five to ten minutes, so print a progress line every 10,000 iterations (the current count and password) to confirm the program is still moving. Because the passwords are sorted alphabetically, how far into the alphabet you are tells you roughly how close you are to done.
(Optional, but recommended.) Stop reusing passwords across sites. Memorize them, or use a password manager. According to Snowden the NSA can guess up to a trillion passwords a second, and even an ordinary laptop running John the Ripper manages millions, so pick passwords that are genuinely hard to guess; XKCD 936 has a good method.
Submit
Commit and sync cowsay/, pyproject.toml, password_cracker.py, and whitehouse_secrets/whitehouse_secrets.txt in your lab-cli-tools repository. On Gradescope, choose GitHub and submit that repository and the branch containing your work to lab-cli-tools. Submit once for the whole week. You may resubmit as you finish more steps.
Points for Each Step
Your score is the sum of the steps that pass. Each row earns its own points; unfinished steps do not erase completed work. All checks run automatically. You may resubmit as you finish more steps. A function’s check includes its published examples and additional inputs for that same function. Keep unfinished Python bodies valid with pass: a syntax error can prevent other checks that import that file from running. Fractional points are added before the final score is rounded to four decimal places.
| Graded Step | Points |
|---|---|
| Cowsay: package installs a command | 0.5 |
| Cowsay: installed entry point runs | 0.5 |
| Cowsay: command prints the supplied message | 1 |
| Cowsay: cow drawing | 0.5 |
| Cowsay: speech bubble | 0.5 |
| Cowsay: flake8 style | 1 |
| Cracker: committed decrypted artifact | 1 |
| Cracker: reads the supplied password list | 1 |
| Cracker: survives wrong guesses and prints the successful password | 1 |
| Cracker: extracts the independently encrypted file | 1 |
| Total | 8 |