Command-line interface (CLI) encryption and decryption system that implements the Caesar and Vigenère algorithms over an extended alphabet of real characters, designed to protect text notes that include uppercase, lowercase, punctuation, and digits — just as a journalist writes.
The cipher.py program allows encrypting or decrypting any text using two
classical cryptographic algorithms. It operates on its own extended alphabet
(not the standard 26 letters), which allows processing natural text with
uppercase, lowercase, numbers, and punctuation without losing information.
The behavior for characters outside the alphabet is controlled by the
--normalize flag, which can be strict (removes them) or lax (preserves them).
The system defines a fixed, ordered, and immutable alphabet of N symbols
(N = len(ALPHABET), calculated dynamically). This value is the modulus for
all arithmetic encryption operations.
Positions Content
────────── ────────────────────────────────────────
0 – 25 lowercase letters: a b c ... z
26 – 51 uppercase letters: A B C ... Z
52 – 61 digits: 0 1 2 ... 9
62 space: ' '
63 – 70 basic punctuation: . , ; : ! ? ¡ ¿
71 – 76 secondary punctuation: - _ ( ) " '
77 – 78 text control: \n \t
79 – 98 additional symbols: @ # $ % & + * / = < > [ ] { } | \ ^ ~ `
Alphabet Rules:
- The position of
cis obtained withALPHABET.index(c)→ integer in[0, N-1] - The character at position
iis obtained withALPHABET[i] - Any absent character is considered outside the alphabet (e.g.
é,ñ,ü) - The alphabet remains immutable throughout the execution
Operates with an integer key K in the range [1, N-1].
Encryption: C_i = (P_i + K) mod N
Decryption: P_i = (C_i - K) mod N
Where:
P_i = ALPHABET.index(plaintext[i])— position of character i in the plaintextK— fixed shift, identical for all charactersC_i— position of the encrypted character, mapped back toALPHABET[C_i]N = len(ALPHABET)— modulus of the operation (alphabet size)
Key characteristic: it is a special case of Vigenère with a key of length 1. Vulnerable to frequency analysis since each character in the alphabet always transforms into the same encrypted character (monoalphabetic substitution).
Operates with a text key K of length m >= 2, where all its characters
belong to the alphabet.
Encryption: C_i = (P_i + K_{i mod m}) mod N
Decryption: P_i = (C_i - K_{i mod m}) mod N
Where:
P_i = ALPHABET.index(plaintext[i])— position of character i in the plaintextm = len(K)— length of the key, defines the repetition periodK_{i mod m} = ALPHABET.index(K[i mod m])— numerical value of the key character at positioni mod m- The key repeats cyclically over the message
Key characteristic: by using the extended alphabet (N >> 26), the key can
be an entire phrase like "secret notes" or "editor 2024", which greatly
expands the keyspace and resistance to frequency analysis compared to the
classic 26-letter Vigenère.
| Criterion | Caesar | Vigenère |
|---|---|---|
| Type | Monoalphabetic | Polyalphabetic |
| Keyspace | N-1 = ~98 | N^m (grows with m) |
| Frequency analysis resistance | None | High (depends on m) |
| Brute force attack | Trivial | Unfeasible for large m |
| Classic vulnerability | Always | Kasiski examination (short m) |
- Python 3.8 or higher
- No external dependencies — standard library only
- Text files in UTF-8 encoding
python cipher.py --cipher {caesar|vigenere}
--mode {enc|dec}
--key <key>
--in <input_file | ->
--out <output_file | ->
--normalize {strict|lax}
Flags:
| Flag | Valid values | Description |
|---|---|---|
--cipher |
caesar, vigenere |
Algorithm to use |
--mode |
enc, dec |
enc = encrypt, dec = decrypt |
--key |
integer or text | Key (see restrictions per algorithm below) |
--in |
path or - |
UTF-8 input file or stdin |
--out |
path or - |
UTF-8 output file or stdout |
--normalize |
strict, lax |
Behavior for characters outside the alphabet |
Key Restrictions:
- Caesar: integer in the range
[1, N-1] - Vigenère: text of length
>= 2; all its characters must be in the alphabet
| # | Full Command | Input | Expected Behavior |
|---|---|---|---|
| 1 | python cipher.py --cipher caesar --mode enc --key 3 --in - --out - --normalize strict |
abc |
def (alphabet shift of 3) |
| 2 | python cipher.py --cipher caesar --mode dec --key 3 --in - --out - --normalize strict |
def |
abc (decrypts the previous case) |
| 3 | python cipher.py --cipher caesar --mode enc --key 5 --in - --out - --normalize lax |
Héllo |
é is preserved; H, l, l, o encrypted with K=5 |
| 4 | python cipher.py --cipher caesar --mode enc --key 7 --in sample_input.txt --out - --normalize strict |
(file) | Encrypted text without accented characters |
| 5 | python cipher.py --cipher vigenere --mode enc --key secreto --in - --out - --normalize strict |
El periodista |
Polyalphabetic encrypted text |
| 6 | python cipher.py --cipher vigenere --mode dec --key secreto --in - --out - --normalize strict |
(output from case 5) | Exact restoration of El periodista |
| 7 | python cipher.py --cipher vigenere --mode enc --key "notas secretas" --in - --out - --normalize lax |
Héllo, mundo! |
é is preserved; the rest is encrypted with phrase key |
| 8 | python cipher.py --cipher vigenere --mode enc --key "editor 2024" --in sample_input.txt --out cifrado.txt --normalize lax |
(file) | Encrypted file, accents preserved |
Only characters that belong to the defined alphabet are processed. Any character outside the alphabet is removed before encrypting or decrypting.
Input: "Héllo, mundo!"
Chars: H é l l o , ' ' m u n d o !
In alph: ✓ ✗ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓
Result: "Hllo, mundo!" ← the 'é' was removed
When to use: when you want to guarantee that the encrypted text contains only characters from the alphabet and losing invalid characters is acceptable.
Characters in the alphabet are encrypted normally. Characters outside the alphabet are preserved in their original position in the output, without being encrypted and without advancing the key index.
Input: H é l l o
In alph: ✓ ✗ ✓ ✓ ✓
Action: enc pass enc enc enc
Output: X é Y Z W ← 'é' in its position, rest encrypted
The key index in Vigenère only advances for H, l, l, o — not for é.
When to use: when you want to preserve the original format of the text (with accents, tildes, special characters) but encrypt everything possible.
python tests.pyThe suite runs 22 test cases and reports [PASS] or [FAIL] for each one,
with debugging details on failed cases. Exits with code
0 if all tests pass, 1 if any fail.
To quickly test the program using the provided sample_input.txt file, follow these steps in your terminal (command line):
Step 1: Encrypt the file
We will encrypt the message using the Vigenère algorithm and the key secreto. Run this command:
python cipher.py --cipher vigenere --mode enc --key secreto --in sample_input.txt --out cifrado.txt --normalize laxStep 2: Check the encrypted text
Open the recently created cifrado.txt file. You will see that the original message is now an incomprehensible mix of characters (although it will keep accents and line breaks thanks to --normalize lax).
Step 3: Decrypt the message
Now we will reverse the operation to recover your text. We take cifrado.txt as input and create descifrado.txt:
python cipher.py --cipher vigenere --mode dec --key secreto --in cifrado.txt --out descifrado.txt --normalize laxStep 4: Verify success
Open the recently created descifrado.txt file. You should see exactly the same text from sample_input.txt:
El periodista llegó al punto de encuentro a las 3:00am. Nadie lo esperaba. ¿Sería una trampa? No podía saberlo. Guardó sus notas en el bolsillo izquierdo y siguió caminando.