X Xerobit

ROT13 in Programming — Implementing and Using ROT13 in Code

ROT13 is simple to implement but appears in real codebases for obfuscation, puzzle games, and filter evasion. Here's how to implement ROT13 in JavaScript, Python, and Go — and...

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 →

ROT13 is one of the simplest cipher algorithms — rotate each letter 13 positions. Because the English alphabet has 26 letters, applying ROT13 twice returns the original text. Here’s clean implementations in several languages.

Use the ROT13 Cipher to encode and decode ROT13 text online.

JavaScript implementations

Concise regex version

const rot13 = str =>
  str.replace(/[a-zA-Z]/g, c => {
    const base = c <= 'Z' ? 65 : 97;
    return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base);
  });

rot13('Hello World');   // 'Uryyb Jbeyq'
rot13('Uryyb Jbeyq');   // 'Hello World' (same function decodes)

Readable version

function rot13(text) {
  return text.split('').map(char => {
    const code = char.charCodeAt(0);
    
    // Uppercase A-Z (65-90):
    if (code >= 65 && code <= 90) {
      return String.fromCharCode(((code - 65 + 13) % 26) + 65);
    }
    
    // Lowercase a-z (97-122):
    if (code >= 97 && code <= 122) {
      return String.fromCharCode(((code - 97 + 13) % 26) + 97);
    }
    
    // Non-letter: pass through unchanged
    return char;
  }).join('');
}

Lookup table version

const ROT13_TABLE = {};
'abcdefghijklmnopqrstuvwxyz'.split('').forEach((c, i) => {
  const rotated = 'abcdefghijklmnopqrstuvwxyz'[(i + 13) % 26];
  ROT13_TABLE[c] = rotated;
  ROT13_TABLE[c.toUpperCase()] = rotated.toUpperCase();
});

const rot13 = str =>
  str.split('').map(c => ROT13_TABLE[c] || c).join('');

The lookup table is faster than the arithmetic version for repeated calls.

Python implementations

Using codecs (built-in)

import codecs

rot13_text = codecs.encode('Hello World', 'rot_13')
# 'Uryyb Jbeyq'

# Decode (same operation):
codecs.encode('Uryyb Jbeyq', 'rot_13')
# 'Hello World'

# Or use decode:
'Hello World'.encode().decode('rot_13')
# AttributeError: Python 3 str doesn't have rot_13 codec on str directly
# Use codecs.encode instead

Manual implementation

def rot13(text: str) -> str:
    result = []
    for char in text:
        if 'a' <= char <= 'z':
            result.append(chr((ord(char) - ord('a') + 13) % 26 + ord('a')))
        elif 'A' <= char <= 'Z':
            result.append(chr((ord(char) - ord('A') + 13) % 26 + ord('A')))
        else:
            result.append(char)
    return ''.join(result)

rot13('Hello World')   # 'Uryyb Jbeyq'
rot13('Uryyb Jbeyq')  # 'Hello World'

Using str.translate

import string

def make_rot13_table():
    lower = string.ascii_lowercase
    upper = string.ascii_uppercase
    rotated_lower = lower[13:] + lower[:13]
    rotated_upper = upper[13:] + upper[:13]
    return str.maketrans(lower + upper, rotated_lower + rotated_upper)

ROT13_TABLE = make_rot13_table()

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

# Using str.translate is faster than character-by-character for long strings
rot13('Hello World')  # 'Uryyb Jbeyq'

Go implementation

package main

import "unicode"

func ROT13(s string) string {
    return strings.Map(func(r rune) rune {
        switch {
        case r >= 'A' && r <= 'Z':
            return 'A' + (r-'A'+13)%26
        case r >= 'a' && r <= 'z':
            return 'a' + (r-'a'+13)%26
        }
        return r
    }, s)
}

Where ROT13 appears in real code

Test data obfuscation

// Obscure sensitive-looking test strings in source:
const TEST_PASSWORD = rot13('cnffjbeq123!');  // 'password123!'
const TEST_EMAIL = rot13('grfg@rknzcyr.pbz'); // 'test@example.com'

Prevents tools from flagging hardcoded credentials in test files.

Puzzle and game code

// In a game or ARG:
const SECRET_MESSAGE = 'Gur nafjre vf 42';  // 'The answer is 42'

function decode(msg) {
  return rot13(msg);  // Players discover this function
}

Avoiding string detection

# Avoid keyword filters in technical docs:
FILTER_KEYWORD = rot13('ivnten')  # 'viagra' — demo of spam evasion concept

In configuration or data storage

Some older systems use ROT13 to “hide” config values from casual viewers (not secure, just obfuscated).

ROT13 variants

// ROT5 for digits (rotate 0-9 by 5):
const rot5 = str =>
  str.replace(/[0-9]/g, n =>
    String.fromCharCode(((n.charCodeAt(0) - 48 + 5) % 10) + 48)
  );

// ROT18 = ROT13 for letters + ROT5 for digits:
const rot18 = str => rot13(rot5(str));

// ROT47 for all printable ASCII (33-126):
const rot47 = str =>
  str.replace(/[!-~]/g, c =>
    String.fromCharCode(((c.charCodeAt(0) - 33 + 47) % 94) + 33)
  );

rot47('Hello World!');  // 'w6==@ (@C=5P'

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.