Skip to content

How to Implement a Simple Substitution Cipher in Batch Script

Learn how to build a ROT13-style substitution cipher in Batch with three methods: character-by-character mapping, collision-safe bulk replacement, and PowerShell Base64 encoding.

A substitution cipher is a method of encrypting text by replacing each character with another character according to a fixed system. While it is not secure enough for sensitive data by modern standards, it is an excellent exercise for mastering string slicing, character replacement, and loop-based text processing in Batch.

In this guide, we will demonstrate how to build a simple ROT13-style encoder/decoder and a more flexible map-based substitution script.

Method 1: The Character-by-Character Mapping

This method iterates through the input string one character at a time, looks up each character's position in a source alphabet, and substitutes the character at the same position in a cipher alphabet. By defining an "Alpha" string and its corresponding "Cipher" map, we build the encrypted output in a separate variable, avoiding collision issues entirely.

Implementation Script

@echo off
setlocal enabledelayedexpansion

:: Define the standard alphabet and the shifted cipher
:: In this example, A becomes N, B becomes O, etc. (ROT13)
set "Alpha=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
set "Cipher=NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm"

set "InputText="
set /p "InputText=Enter text to process: "

if not defined InputText (
    echo [ERROR] No input provided.
    pause
    exit /b 1
)

set "OutputText="

:: Loop through every character in the input string
set "Index=0"
:ProcessLoop
set "Char=!InputText:~%Index%,1!"
if not defined Char goto :DisplayResult

:: Find the character's position in the Alpha string
set "Found=0"
for /L %%i in (0, 1, 51) do (
    if !Found! equ 0 (
        if "!Char!"=="!Alpha:~%%i,1!" (
            set "MapChar=!Cipher:~%%i,1!"
            set "OutputText=!OutputText!!MapChar!"
            set "Found=1"
        )
    )
)

:: If character was not in alphabet (e.g., space, digit, punctuation), keep it unchanged
if !Found! equ 0 set "OutputText=!OutputText!!Char!"

set /a "Index+=1"
goto :ProcessLoop

:DisplayResult
echo.
echo Original: !InputText!
echo Result:   !OutputText!
echo.
pause
exit /b 0

How It Works

  1. The script extracts one character at a time from InputText using !InputText:~%Index%,1!.
  2. It compares that character against every position in the Alpha string using a for /L loop.
  3. When a match is found, it retrieves the character at the same position in the Cipher string and appends it to OutputText.
  4. Characters not found in Alpha (spaces, digits, punctuation) are appended unchanged.
  5. The loop advances the index and repeats until no more characters remain.

Method 2: Bulk String Replacement with Collision Avoidance

The character-by-character loop in Method 1 is precise but verbose. Batch's native string replacement (!var:OLD=NEW!) can transform an entire string in one operation, but it introduces a critical problem: if you replace A with B and then B with C, every original A becomes C instead of B. This is called a collision.

The solution is a two-pass approach using temporary placeholder strings that do not appear in the input.

Implementation Script

@echo off
setlocal enabledelayedexpansion

set "Message="
set /p "Message=Enter message: "

if not defined Message (
    echo [ERROR] No input provided.
    pause
    exit /b 1
)

set "Result=!Message!"

:: Example cipher: Swap A<->Z, B<->Y, C<->X (and lowercase equivalents)
:: Pass 1: Replace source characters with unique placeholders
set "Result=!Result:A=__01__!"
set "Result=!Result:Z=__02__!"
set "Result=!Result:B=__03__!"
set "Result=!Result:Y=__04__!"
set "Result=!Result:C=__05__!"
set "Result=!Result:X=__06__!"
set "Result=!Result:a=__07__!"
set "Result=!Result:z=__08__!"
set "Result=!Result:b=__09__!"
set "Result=!Result:y=__10__!"
set "Result=!Result:c=__11__!"
set "Result=!Result:x=__12__!"

:: Pass 2: Replace placeholders with target characters
set "Result=!Result:__01__=Z!"
set "Result=!Result:__02__=A!"
set "Result=!Result:__03__=Y!"
set "Result=!Result:__04__=B!"
set "Result=!Result:__05__=X!"
set "Result=!Result:__06__=C!"
set "Result=!Result:__07__=z!"
set "Result=!Result:__08__=a!"
set "Result=!Result:__09__=y!"
set "Result=!Result:__10__=b!"
set "Result=!Result:__11__=x!"
set "Result=!Result:__12__=c!"

echo.
echo Original: !Message!
echo Encoded:  !Result!
echo.
pause

Why Placeholders Are Necessary

Without placeholders, a direct replacement chain produces incorrect results:

:: WRONG - Collision: A→Z, then Z→A reverses the first replacement
set "Result=!Result:A=Z!"
set "Result=!Result:Z=A!"
:: Every original A becomes Z, then immediately becomes A again

The two-pass approach ensures that no replacement can interfere with another, because the intermediate placeholder strings (__01__, __02__, etc.) are guaranteed not to appear in normal input text.

Method 3: PowerShell Base64 Encoding

If your goal is simple obfuscation rather than a letter-swap exercise, Base64 is the industry standard. While technically an encoding rather than a cipher, it hides plain text from casual observation and handles special characters much more reliably than native Batch set commands.

Implementation Script

@echo off
setlocal enabledelayedexpansion

set "Original=Secret Message 123!"

:: Encode to Base64
set "Encoded="
for /f "delims=" %%I in ('powershell -NoProfile -Command ^
  "[Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('!Original!'))"') do (
    set "Encoded=%%I"
)

if not defined Encoded (
    echo [ERROR] Encoding failed.
    pause
    exit /b 1
)

:: Decode from Base64
set "Decoded="
for /f "delims=" %%I in ('powershell -NoProfile -Command ^
  "[System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('!Encoded!'))"') do (
    set "Decoded=%%I"
)

echo Original: !Original!
echo Encoded:  !Encoded!
echo Decoded:  !Decoded!

:: Verify round-trip integrity
if "!Decoded!"=="!Original!" (
    echo.
    echo [OK] Round-trip verification passed.
) else (
    echo.
    echo [WARNING] Decoded text does not match original.
    echo          The input may contain characters that break Batch variable expansion.
)

pause

Common Mistakes

The Wrong Way: Replacing Characters In-Place Without Collision Protection

:: WRONG - Replacements interfere with each other
set "Text=ABCABC"
set "Text=!Text:A=B!"
set "Text=!Text:B=C!"
set "Text=!Text:C=D!"
echo !Text!
:: Expected: BCDBC  Actual: DDDDDD

The Wrong Way: Using %Char% Instead of !Char! Inside Loops

:: WRONG - %Char% is expanded at parse time, not at runtime
set "Char=!InputText:~%Index%,1!"
if "%Char%"=="" goto :DisplayResult

Best Practices

  1. Handle special characters defensively: Native Batch is fragile when processing characters like ^, %, &, <, >, and !. If your cipher script encounters these in the input, set commands may break or produce incorrect results. Use delayed expansion and test with special-character inputs.
  2. Use explicit case handling: Batch string replacement is case-insensitive by default. In Method 1, we include both uppercase and lowercase characters in the Alpha and Cipher strings to ensure A and a map to different cipher characters. In Method 2, separate placeholders preserve case distinction.
  3. Avoid in-place collision: Never run multiple set "var=!var:A=B!" replacements on the same variable for a substitution cipher. Use either character-by-character output building (Method 1) or two-pass placeholder replacement (Method 2).
  4. Use Base64 for practical obfuscation: For real-world obfuscation of configuration values or non-critical strings, Base64 (Method 3) is more reliable and handles the full character set without Batch parsing issues.
  5. Never use this for security: Batch-based substitution ciphers provide zero cryptographic security. They are strictly for educational purposes, simple obfuscation, or adding personality to automation scripts. For actual encryption, use PowerShell with [System.Security.Cryptography] classes or dedicated tools.

Conclusion

Implementing a substitution cipher in Batch forces a deep dive into string manipulation and loop control.

  • The character-by-character approach (Method 1) provides the most correct and collision-free implementation by building the output in a separate variable.
  • The bulk replacement approach (Method 2) is faster but requires careful two-pass placeholder logic to prevent replacement collisions.
  • For practical obfuscation needs, PowerShell Base64 encoding (Method 3) handles the full character set reliably.

Regardless of the method chosen, the exercise builds essential skills in Batch string slicing, delayed expansion, and iterative processing.

Navigation