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 0How It Works
- The script extracts one character at a time from
InputTextusing!InputText:~%Index%,1!. - It compares that character against every position in the
Alphastring using afor /Lloop. - When a match is found, it retrieves the character at the same position in the
Cipherstring and appends it toOutputText. - Characters not found in
Alpha(spaces, digits, punctuation) are appended unchanged. - 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.
pauseWhy 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 againThe 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.
)
pauseCommon 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: DDDDDDThe 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 :DisplayResultBest Practices
- Handle special characters defensively: Native Batch is fragile when processing characters like
^,%,&,<,>, and!. If your cipher script encounters these in the input,setcommands may break or produce incorrect results. Use delayed expansion and test with special-character inputs. - Use explicit case handling: Batch string replacement is case-insensitive by default. In Method 1, we include both uppercase and lowercase characters in the
AlphaandCipherstrings to ensureAandamap to different cipher characters. In Method 2, separate placeholders preserve case distinction. - 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). - 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.
- 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.