X Xerobit

ROT13 in Python — Encode, Decode, and Extend to ROT47

Implement ROT13 encoding in Python using codecs, str.translate, or a manual lookup table. Extends to ROT47 (all printable ASCII), ROT5 (digits), and ROT18 (combined). Includes...

Mian Ali Khalid · · 4 min read
Use the tool
ROT13 / Caesar Cipher
Encode and decode ROT13 and arbitrary Caesar shifts. Letter frequency analysis. 100% client-side.
Open ROT13 / Caesar Cipher →

Python has built-in ROT13 support via the codecs module. For ROT47 or custom rotations, use str.maketrans() to create fast lookup tables.

Encode and decode ROT13 online with the ROT13 Cipher.

Built-in ROT13 with codecs

import codecs

# Encode:
encoded = codecs.encode('Hello, World!', 'rot_13')
print(encoded)  # "Uryyb, Jbeyq!"

# Decode (same operation — ROT13 is its own inverse):
decoded = codecs.decode('Uryyb, Jbeyq!', 'rot_13')
print(decoded)  # "Hello, World!"

# Or using the 'rot13' alias:
encoded = 'Hello, World!'.encode('rot_13')
decoded = encoded.decode('rot_13')

Manual ROT13 with str.maketrans

ROT13_TABLE = str.maketrans(
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
    'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm'
)

def rot13(text: str) -> str:
    return text.translate(ROT13_TABLE)

print(rot13('Hello, World!'))  # "Uryyb, Jbeyq!"
print(rot13('Uryyb, Jbeyq!'))  # "Hello, World!"

Generic Caesar cipher

def caesar(text: str, shift: int) -> str:
    """Apply a Caesar cipher with given shift to letters only."""
    result = []
    for char in text:
        if char.isalpha():
            base = ord('A') if char.isupper() else ord('a')
            shifted = (ord(char) - base + shift) % 26 + base
            result.append(chr(shifted))
        else:
            result.append(char)
    return ''.join(result)

# ROT13 is shift=13:
rot13 = lambda text: caesar(text, 13)

# Brute-force all 25 shifts:
for shift in range(1, 26):
    print(f"ROT{shift:2d}: {caesar('Uryyb', shift)}")

ROT47 — all printable ASCII

ROT47 rotates the full range of printable ASCII characters (33–126, 94 total characters):

def rot47(text: str) -> str:
    """ROT47: rotate all printable ASCII characters."""
    result = []
    for char in text:
        code = ord(char)
        if 33 <= code <= 126:  # Printable ASCII range
            rotated = 33 + (code - 33 + 47) % 94
            result.append(chr(rotated))
        else:
            result.append(char)
    return ''.join(result)

print(rot47('Hello, World!'))  # "w6==@[ (@C=5P"
print(rot47('w6==@[ (@C=5P'))  # "Hello, World!"

# Faster with maketrans:
ROT47_TABLE = str.maketrans(
    ''.join(chr(i) for i in range(33, 127)),
    ''.join(chr(33 + (i - 33 + 47) % 94) for i in range(33, 127))
)

def rot47_fast(text: str) -> str:
    return text.translate(ROT47_TABLE)

ROT5 (digits only) and ROT18 (combined)

def rot5(text: str) -> str:
    """Rotate digits 0-9 by 5."""
    table = str.maketrans('0123456789', '5678901234')
    return text.translate(table)

def rot18(text: str) -> str:
    """ROT18 = ROT13 for letters + ROT5 for digits."""
    table = str.maketrans(
        'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
        'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm5678901234'
    )
    return text.translate(table)

print(rot18('Hello World 2026!'))  # "Uryyb Jbeyq 7571!"

Command-line ROT13 tool

#!/usr/bin/env python3
"""rot13.py — encode/decode ROT13 from command line or file."""
import sys
import codecs
import argparse

def main():
    parser = argparse.ArgumentParser(description='ROT13 encoder/decoder')
    parser.add_argument('text', nargs='?', help='Text to encode (or read from stdin)')
    parser.add_argument('-f', '--file', help='Input file path')
    parser.add_argument('--rot47', action='store_true', help='Use ROT47 instead of ROT13')
    args = parser.parse_args()

    if args.file:
        with open(args.file, 'r', encoding='utf-8') as f:
            text = f.read()
    elif args.text:
        text = args.text
    else:
        text = sys.stdin.read()

    if args.rot47:
        table = str.maketrans(
            ''.join(chr(i) for i in range(33, 127)),
            ''.join(chr(33 + (i - 33 + 47) % 94) for i in range(33, 127))
        )
        result = text.translate(table)
    else:
        result = codecs.encode(text, 'rot_13')

    print(result, end='')

if __name__ == '__main__':
    main()
# Usage:
python rot13.py "Hello, World!"          # Uryyb, Jbeyq!
python rot13.py "Uryyb, Jbeyq!"          # Hello, World!
python rot13.py --rot47 "Hello, World!"  # w6==@[ (@C=5P
echo "Hello" | python rot13.py           # Uryyb
python rot13.py -f spoiler.txt           # Decode a file

Related posts

Related tool

ROT13 / Caesar Cipher

Encode and decode ROT13 and arbitrary Caesar shifts. Letter frequency analysis. 100% client-side.

Written by Mian Ali Khalid. Part of the Encoding & Crypto pillar.