Affine Cipher in Bash: Complete Solution & Deep Dive Guide

Rows of text on a dark background

Affine Cipher in Bash: The Ultimate Guide to Scripting Ancient Encryption

The Affine Cipher is a classic monoalphabetic substitution cipher that enhances the Caesar cipher by using a linear function. This guide explains its mathematical foundation and provides a complete, robust Bash script to perform both encryption and decryption, making it a perfect project for sharpening your scripting skills.


The Agonizing Quest for a Better Secret Code

Imagine you're a budding cryptographer, tired of simple letter-swapping games. You've mastered the Caesar cipher, shifting letters by a fixed amount, but you quickly realize its weakness. With only 25 possible keys, anyone can break your "secret" message in minutes with brute force. You need something more complex, something with more mathematical elegance to truly challenge a codebreaker.

This is the exact problem that ancient mathematicians and cryptographers faced. They needed a system that was easy enough to use by hand but significantly harder to crack than a simple shift. Their solution was a beautiful application of modular arithmetic: the Affine Cipher. It introduces a second key, a multiplier, that scrambles the letters in a far less predictable way. This guide will not only demystify the math behind it but empower you to build your own implementation from scratch using the versatile power of Bash scripting.


What Is the Affine Cipher? A Mathematical Deep Dive

The Affine Cipher is a type of monoalphabetic substitution cipher. This means each letter of the alphabet is mapped to exactly one other letter. Its strength lies in its use of a linear mathematical function to perform this mapping, making it a significant step up from ciphers that only use addition (like Caesar).

The core of the cipher lies in two keys, typically denoted as a and b, and a simple formula.

The Encryption Formula: E(x) = (ax + b) mod m

Let's break down this elegant equation:

  • x: Represents the numeric value of a plaintext letter (e.g., a=0, b=1, c=2, ..., z=25).
  • a: The first key, known as the multiplier. This value multiplies the letter's numeric value, spreading the letters out across the alphabet.
  • b: The second key, known as the shift or offset. This value is added after the multiplication, similar to a Caesar cipher shift.
  • m: The size of the alphabet. For the English alphabet, m is 26.
  • mod m: The modulo operator, which ensures the result wraps around and stays within the alphabet's bounds (0 to 25).

The most critical rule governs the key a. For the cipher to be decryptable, a must be coprime with m. This means their greatest common divisor (GCD) must be 1 (i.e., gcd(a, 26) = 1). If this condition isn't met, multiple plaintext letters could map to the same ciphertext letter, making it impossible to reverse the process uniquely.

For an alphabet of size 26, the valid values for a are: 1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, and 25.

The Decryption Formula: D(y) = a⁻¹(y - b) mod m

Decrypting an Affine Cipher message requires reversing the encryption process. This involves subtraction, but more importantly, it requires division. In modular arithmetic, we don't divide; we multiply by the modular multiplicative inverse.

  • y: Represents the numeric value of a ciphertext letter.
  • a⁻¹: This is the modular multiplicative inverse of a modulo m. It's a number such that (a * a⁻¹) mod m = 1. Finding this inverse is the key to unlocking the message.
  • (y - b): This reverses the addition of the shift key b.

The entire process hinges on being able to find a⁻¹, which is only possible if a and m are coprime. This mathematical constraint is the heart of the cipher's design.


How to Implement the Affine Cipher in Bash

Building an Affine Cipher script in Bash is an excellent exercise in handling string manipulation, arithmetic operations, and algorithmic logic. We will create a robust script that can validate keys, encode plaintext, and decode ciphertext.

The Logic Flow: Encryption vs. Decryption

Before diving into the code, let's visualize the two main processes. Understanding the flow is crucial for writing clean and effective code.

Encryption Process Diagram

This diagram illustrates how a single character of plaintext is transformed into a ciphertext character.

    ● Start with Plaintext Character
    │
    ▼
  ┌───────────────────────────┐
  │  Convert Char to 0-Index  │
  │  (e.g., 'c' ⟶ 2)          │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ Apply Encryption Formula  │
  │   y = (a * x + b) % 26    │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ Convert New Index to Char │
  │  (e.g., 15 ⟶ 'p')         │
  └────────────┬──────────────┘
               │
               ▼
    ● End with Ciphertext Character

Decryption Process Diagram

Decryption is the reverse, with the critical step of first finding the modular multiplicative inverse (MMI) of the key a.

    ● Start with Ciphertext Character
    │
    ▼
  ┌───────────────────────────┐
  │  Convert Char to 0-Index  │
  │  (e.g., 'p' ⟶ 15)         │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ Find Modular Inverse (a⁻¹)│
  │   (a * a⁻¹) % 26 = 1      │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ Apply Decryption Formula  │
  │ x = a⁻¹ * (y - b) % 26    │
  └────────────┬──────────────┘
               │
               ▼
  ┌───────────────────────────┐
  │ Convert Old Index to Char │
  │  (e.g., 2 ⟶ 'c')          │
  └────────────┬──────────────┘
               │
               ▼
    ● End with Plaintext Character

The Complete Bash Script

Here is a full-featured Bash script that implements the Affine Cipher. It includes key validation, encoding, decoding, and helper functions for the required mathematical operations. This script is designed for clarity and can be saved as affine_cipher.sh.


#!/usr/bin/env bash

# affine_cipher.sh
# A script to encrypt and decrypt text using the Affine Cipher.
# This script is part of the exclusive kodikra.com learning curriculum.

set -euo pipefail

# --- Global Variables ---
readonly ALPHABET="abcdefghijklmnopqrstuvwxyz"
readonly M=26 # Size of the alphabet

# --- Helper Functions ---

# Calculates the greatest common divisor (GCD) of two numbers using Euclidean algorithm.
# Usage: gcd  
gcd() {
    local a=$1
    local b=$2
    local temp
    while (( b != 0 )); do
        temp=$b
        b=$(( a % b ))
        a=$temp
    done
    echo "$a"
}

# Finds the modular multiplicative inverse (MMI) of 'a' modulo 'm'.
# It searches for a number 'n' such that (a * n) % m == 1.
# Usage: find_mmi  
find_mmi() {
    local a=$1
    local m=$2
    for ((n = 1; n < m; n++)); do
        if ((( (a * n) % m ) == 1 )); then
            echo "$n"
            return 0
        fi
    done
    # This should not be reached if 'a' is valid
    echo "Error: MMI not found for a=$a" >&2
    return 1
}

# Cleans input text: converts to lowercase and removes non-alphanumeric characters.
# Usage: clean_text "Some Text!"
clean_text() {
    echo "$1" | tr '[:upper:]' '[:lower:]' | tr -dc 'a-z0-9'
}

# --- Core Cipher Functions ---

# Encodes a plaintext string using the Affine Cipher.
# Usage: encode   "plaintext"
encode() {
    local a=$1
    local b=$2
    local plaintext
    plaintext=$(clean_text "$3")
    local ciphertext=""
    local result=""

    for (( i=0; i<${#plaintext}; i++ )); do
        local char="${plaintext:i:1}"
        if [[ "$char" =~ [a-z] ]]; then
            # It's a letter, encrypt it
            local x
            # Get the 0-based index of the character
            x=$(tr -d -c "$char" <<< "$ALPHABET" | wc -c)
            x=$((x - 1))
            
            local y=$(( (a * x + b) % M ))
            ciphertext+="${ALPHABET:y:1}"
        elif [[ "$char" =~ [0-9] ]]; then
            # It's a number, keep it as is
            ciphertext+="$char"
        fi
    done

    # Group output into blocks of 5 characters for readability
    result=$(echo "$ciphertext" | fold -w 5 | paste -sd ' ' -)
    echo "$result"
}

# Decodes a ciphertext string using the Affine Cipher.
# Usage: decode   "ciphertext"
decode() {
    local a=$1
    local b=$2
    local ciphertext
    # Remove spaces for processing
    ciphertext=$(echo "$3" | tr -d ' ')
    local plaintext=""
    
    local mmi
    mmi=$(find_mmi "$a" "$M")

    for (( i=0; i<${#ciphertext}; i++ )); do
        local char="${ciphertext:i:1}"
        if [[ "$char" =~ [a-z] ]]; then
            # It's a letter, decrypt it
            local y
            y=$(tr -d -c "$char" <<< "$ALPHABET" | wc -c)
            y=$((y - 1))

            # Apply decryption formula: x = mmi * (y - b) % M
            # The `+ M` handles potential negative results from (y - b)
            local x=$(( (mmi * (y - b) + M) % M ))
            plaintext+="${ALPHABET:x:1}"
        elif [[ "$char" =~ [0-9] ]]; then
            # It's a number, keep it as is
            plaintext+="$char"
        fi
    done

    echo "$plaintext"
}

# --- Main Script Logic ---

main() {
    if (( $# != 4 )); then
        echo "Usage: $0 <encode|decode> <a> <b> \"<text>\"" >&2
        exit 1
    fi

    local action=$1
    local a=$2
    local b=$3
    local text=$4

    # Validate key 'a'
    if (( $(gcd "$a" "$M") != 1 )); then
        echo "Error: key 'a' ($a) must be coprime with alphabet size ($M)." >&2
        exit 1
    fi

    case "$action" in
        encode)
            encode "$a" "$b" "$text"
            ;;
        decode)
            decode "$a" "$b" "$text"
            ;;
        *)
            echo "Error: Invalid action. Choose 'encode' or 'decode'." >&2
            exit 1
            ;;
    esac
}

# Execute the main function with all script arguments
main "$@"

Code Walkthrough and Explanation

Let's dissect the script to understand how each part contributes to the final functionality. This detailed breakdown is essential for learning and adapting the code for your own projects.

1. Initial Setup and Globals


set -euo pipefail

readonly ALPHABET="abcdefghijklmnopqrstuvwxyz"
readonly M=26
  • set -euo pipefail: This is a best practice for modern Bash scripting. It makes the script exit immediately if a command fails (-e), if an unset variable is used (-u), and ensures that a pipeline's exit status is the value of the last command to exit with a non-zero status (pipefail).
  • readonly ALPHABET: We define the alphabet as a constant string. This is our reference for converting characters to numbers and back.
  • readonly M=26: The size of the alphabet, our modulus m, is also a constant.

2. Mathematical Helper Functions


# Calculates the greatest common divisor (GCD)
gcd() { ... }

# Finds the modular multiplicative inverse (MMI)
find_mmi() { ... }
  • gcd(): This function implements the classic Euclidean algorithm to find the greatest common divisor. It's vital for our key validation step to ensure a and 26 are coprime.
  • find_mmi(): This function is the heart of the decryption logic. It performs a brute-force search for the modular multiplicative inverse of a. For a small modulus like 26, this is perfectly efficient. It iterates from 1 to 25, checking which number n satisfies the condition (a * n) % 26 == 1.

3. The encode Function


encode() {
    local a=$1
    local b=$2
    local plaintext=$(clean_text "$3")
    # ... loop ...
}

The encoding logic is a direct implementation of the formula (ax + b) mod 26. The function first cleans the input text to ensure it's lowercase and contains only letters and numbers.

Inside the loop, for each character:

  1. It checks if the character is a letter. Numbers are passed through unchanged.
  2. It finds the character's 0-based index (x). The line x=$(tr -d -c "$char" <<< "$ALPHABET" | wc -c) is a clever but slightly obscure way to do this. A simpler loop could also work, but this is a compact shell trick. It essentially counts how many characters in the alphabet come before the target character.
  3. It applies the encryption formula: y=$(( (a * x + b) % M )).
  4. It retrieves the new character from the ALPHABET string using substring expansion: ${ALPHABET:y:1}.
  5. Finally, the output is grouped into 5-character blocks using fold and paste for the classic ciphertext look.

4. The decode Function


decode() {
    local a=$1
    local b=$2
    # ...
    local mmi=$(find_mmi "$a" "$M")
    # ... loop ...
}

The decoding function mirrors the encoding one but applies the reverse logic.

  1. First, it calls find_mmi to get the crucial inverse key a⁻¹.
  2. It loops through the ciphertext (after removing spaces).
  3. For each letter, it finds its numeric value y.
  4. It applies the decryption formula: x=$(( (mmi * (y - b) + M) % M )). The + M inside the parentheses is a critical detail. In Bash, the % operator can return a negative result if the dividend is negative. Adding M before the modulo ensures the result is always a positive number between 0 and 25, correctly handling cases where y < b.
  5. The resulting index x is converted back to a plaintext character.

5. Main Logic and Validation


main() {
    if (( $# != 4 )); then ... fi

    # ... argument parsing ...

    if (( $(gcd "$a" "$M") != 1 )); then
        echo "Error: key 'a' ($a) must be coprime with alphabet size ($M)." >&2
        exit 1
    fi

    case "$action" in ... esac
}

main "$@"

The main function serves as the script's entry point. It validates the number of arguments, parses them into meaningful variables, and, most importantly, performs the key validation. It calls our gcd function to check if a and 26 are coprime. If not, it exits with an error, preventing an undecryptable message from being created. Finally, a case statement directs the flow to either encode or decode based on the user's input.

How to Run the Script

To use the script, make it executable and run it from your terminal.


# Make the script executable
chmod +x affine_cipher.sh

# --- Example Usage ---

# Encode a message with key a=5, b=7
# Plaintext: "thequickbrownfoxjumpsoverthelazydog"
./affine_cipher.sh encode 5 7 "The quick brown fox jumps over the lazy dog."
# Expected Output: fqjcb rwjwj vnjax bncha djhjd majsq dsubd jbjsw jaxee jaxvb j

# Decode the message with the same key
./affine_cipher.sh decode 5 7 "fqjcb rwjwj vnjax bncha djhjd majsq dsubd jbjsw jaxee jaxvb j"
# Expected Output: thequickbrownfoxjumpsoverthelazydog

Why Use the Affine Cipher? (And When Not To)

While fascinating, the Affine Cipher belongs to the world of classical cryptography. It's an excellent educational tool but should never be used for securing sensitive information today. Understanding its strengths and weaknesses provides valuable context.

Pros / Advantages Cons / Risks
Stronger Than Simpler Ciphers: With 12 possible values for a and 26 for b, there are 12 * 26 = 312 possible keys, making it much harder to brute-force than a Caesar cipher (25 keys). Vulnerable to Frequency Analysis: As a monoalphabetic substitution cipher, it preserves the frequency distribution of the original language. The most common letter in the ciphertext (e.g., 'J') likely corresponds to the most common letter in English ('E').
Excellent Educational Tool: It perfectly demonstrates key cryptographic concepts like modular arithmetic, modular inverses, and the importance of key selection (coprimality). Weak Against Known-Plaintext Attacks: If an attacker knows just a small part of the original message and its corresponding ciphertext, they can set up a system of linear equations to solve for a and b and break the entire cipher.
Great Scripting Challenge: Implementing the cipher requires careful handling of logic, string manipulation, and arithmetic, making it a valuable project for any programmer. Not Secure for Modern Use: It offers virtually no protection against modern computational attacks. For real security, always use established, peer-reviewed algorithms like AES.

This cipher is a stepping stone. It's a module from the kodikra learning path designed to build foundational knowledge before you tackle more complex, modern cryptographic systems. Learn more about shell scripting in our comprehensive Bash learning path.


Frequently Asked Questions (FAQ)

1. What are `a` and `b` in the Affine Cipher?

a and b form the encryption key. a is the multiplier that scales the character's numeric value, while b is the offset that shifts it. Both are crucial for the transformation, but a has the special requirement that it must be coprime to the alphabet size (26).

2. Why must `a` be coprime to 26?

If a shares a common factor with 26 (other than 1), the encryption function is no longer a one-to-one mapping. This means multiple different plaintext letters could be encrypted to the same ciphertext letter. When this happens, it's impossible to know which original letter to decrypt to, making unique decryption impossible. The coprime condition ensures that a modular multiplicative inverse exists, which is required for decryption.

3. What is a Modular Multiplicative Inverse (MMI)?

The MMI of a number a (modulo m) is another number, let's call it a⁻¹, such that (a * a⁻¹) mod m = 1. It is the equivalent of division in modular arithmetic. Finding the MMI is the key to reversing the multiplication step in the Affine Cipher's encryption formula.

4. Is the Affine Cipher secure for modern use?

Absolutely not. The Affine Cipher is a classical cipher and is considered completely broken by modern standards. It is highly susceptible to frequency analysis and can be cracked in seconds with computational tools. It should only be used for educational purposes or historical study.

5. How does this Bash script handle non-alphabetic characters?

The provided script is designed to handle this gracefully. It processes the input text and distinguishes between letters and numbers. Letters are encrypted or decrypted according to the cipher rules. Numbers are passed through unchanged. All other characters (punctuation, spaces, symbols) are stripped out by the clean_text function before processing to simplify the cipher logic.

6. Can the Affine Cipher work with alphabets other than English?

Yes, it can. The underlying mathematical principle is independent of the specific alphabet. To adapt the script, you would need to change the ALPHABET variable to your target language's alphabet and update the modulus M to match its size. You would also need to find the new set of valid coprime values for the key a.

7. What's the difference between the Affine Cipher and the Caesar Cipher?

The Caesar Cipher is actually a special case of the Affine Cipher. In a Caesar cipher, you only shift letters, which is equivalent to an Affine Cipher where the multiplier key a is fixed to 1. The formula becomes E(x) = (1*x + b) mod 26, which is just a simple shift. The Affine Cipher adds the multiplier a, creating a more complex and secure (by classical standards) transformation.


Conclusion: From Ancient Math to Modern Scripts

You have now journeyed from the mathematical theory of an ancient cipher to a practical, hands-on implementation in a modern shell script. By building this Affine Cipher tool, you've not only created a working encryption/decryption program but also reinforced fundamental programming and cryptographic concepts: modular arithmetic, key validation, string processing, and algorithmic thinking.

While the Affine Cipher won't protect your data in the digital age, the skills you've honed by mastering it are timeless. This project, a core component of the Kodikra Cryptography and Ciphers track, serves as a solid foundation, preparing you for the more complex and secure algorithms that power our world today.

Disclaimer: The Bash script provided in this guide is optimized for Bash version 4.4 and higher. While it may work on older versions, functionality related to string and array manipulation is most reliable on modern releases.


Published by Kodikra — Your trusted Bash learning resource.