Rail fence cipher
Write a message in a zigzag across rails and read it row by row: with 3 rails, WEAREDISCOVEREDFLEEATONCE becomes WECRLTEERDSOEEFEAOCAIVDEN, and the same rail count turns it back. Good for puzzles and classroom demos, not for secrets. Free in your browser.
- Free
- Runs in your browser
- No sign-up
- Your data stays on your device
Run — free
Worked example
- Text
- WEAREDISCOVEREDFLEEATONCE
- Number of rails
- 3
- Calculation mode
- Encrypt
- Result
- WECRLTEERDSOEEFEAOCAIVDEN
- Length
- 25
The rail fence cipher is a classical transposition cipher that rearranges characters by writing them in a zigzag pattern down and up across a fixed number of rails, then reading each rail from left to right. Choosing two or more rails scrambles the message without changing letter identities, so spacing and punctuation move with the zigzag rather than being substituted. People use rail fence for puzzle books, CTF warm-ups, classroom demos of transposition versus substitution, and light spoiler obfuscation when a keyed modern cipher is overkill. This capability accepts non-empty text, an integer rail count of at least two, and an optional mode of encrypt or decrypt; it returns the transformed string with normalized mode, rails used, and length.
How the rail fence zigzag works
Encryption writes each successive character onto successive rails while the path travels down from the top rail to the bottom rail and then up again, repeating until the whole message is placed. Reading concatenates every character on the first rail left to right, then the second rail, and so on through the last rail; that concatenation is the ciphertext.
Decryption rebuilds the same occupancy pattern for the known length and rail count, fills each rail with the next slice of ciphertext, and walks the zigzag again to recover the original order. Optional mode defaults to encrypt; encrypt and encode both run the forward zigzag, while decrypt and decode reverse it.
Validation rules, rails limits, and common pitfalls
Whitespace, digits, punctuation, and non-Latin characters are not stripped: every code unit participates in the transposition, so messages keep their symbols while only order changes. Do not treat the result as encryption for confidentiality: anyone who knows the rail count can reverse the scheme quickly, and the product page should never market rail fence as a security control for passwords, tokens, or personal data.
Prefer this landing when the search intent is rail fence cipher, zigzag cipher, or rail-fence encrypt decrypt; Atbash and ROT13 answer substitution intents, and Base64 or hex answer encoding intents, so they stay separate tools. When decrypting, use the same rail count that produced the ciphertext; a wrong rail count yields a different permutation that will not match the original plaintext.
Classroom use and practical limits
Call the free in-browser rail fence cipher for puzzle drafting, lecture slides, CTF practice, and one-off checks without writing a zigzag routine in every language. Support playbooks can reverse lightly scrambled ticket notes that staff intentionally wrote with a known rail count. Puzzle sites can precompute answers for display and reveal them on demand with mode decrypt.
Authors shipping SDKs can map the result field straight into a copy button while mode, rails, and method make OpenAPI examples self-explanatory. Pair with substitution tools only when a later teaching step needs a different cipher family—keep rail fence on its own landing so SEO intent stays sharp around zigzag transposition.
What you can do with it
Puzzle and CTF transposition warm-ups
Hide clues or challenge flags with a known rail count, then decrypt with the same rails when solvers need the reveal without installing crypto libraries.
Classroom transposition demos
Teachers contrast zigzag reordering with substitution ciphers: encrypt, decrypt, and show that letter identities stay the same while order changes.
FAQ
How do I encrypt text with the rail fence cipher?
The response result field holds the zigzag ciphertext.
How do I decrypt rail-fence ciphertext?
Send the ciphertext as text, the same rails value used to encrypt, and mode decrypt (or decode). The result is the reconstructed plaintext when the rail count matches.
What happens if I use fewer than two rails?
The rail-fence product requires at least two rails; one rail would leave the text unchanged.
Is the rail fence cipher tool free?
The browser tool runs free on your device.
Is rail fence secure encryption?
No. Rail fence is a classical transposition for puzzles and teaching, not confidentiality. Never use it for passwords or sensitive data.
Related tools
For developers — API access
Everything on this page is available programmatically. This section is for teams who want to wire it into their own systems; everyone else can just use the tool above.
API endpoint
Prefer to automate it? One authenticated POST creates the task; the result comes back by webhook or a signed link. The same capability also runs here on the web, by email and from Telegram — and soon from our app too.
Call it from your stack
curl -X POST https://api.kit.forhosting.com/enc/rail-fence \
-H "Authorization: Bearer $KIT_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"WEAREDISCOVEREDFLEEATONCE","rails":3}'const res = await fetch("https://api.kit.forhosting.com/enc/rail-fence", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.KIT_KEY}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
"text": "WEAREDISCOVEREDFLEEATONCE",
"rails": 3
})
});
const { task_id } = await res.json();import os, requests
res = requests.post(
"https://api.kit.forhosting.com/enc/rail-fence",
headers={"Authorization": f"Bearer {os.environ['KIT_KEY']}"},
json={
"text": "WEAREDISCOVEREDFLEEATONCE",
"rails": 3
},
)
task_id = res.json()["task_id"]<?php
$res = file_get_contents("https://api.kit.forhosting.com/enc/rail-fence", false, stream_context_create([
"http" => [
"method" => "POST",
"header" => "Authorization: Bearer " . getenv("KIT_KEY") . "\r\nContent-Type: application/json",
"content" => '{"text":"WEAREDISCOVEREDFLEEATONCE","rails":3}',
],
]));
$task = json_decode($res, true);body := bytes.NewBufferString(`{"text":"WEAREDISCOVEREDFLEEATONCE","rails":3}`)
req, _ := http.NewRequest("POST", "https://api.kit.forhosting.com/enc/rail-fence", body)
req.Header.Set("Authorization", "Bearer "+os.Getenv("KIT_KEY"))
req.Header.Set("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)Example request
{
"text": "WEAREDISCOVEREDFLEEATONCE",
"rails": 3
}Example response
{
"task_id": "tsk_a1b2c3d4e5f6a1b2c3d4e5f6",
"type": "enc.rail_fence",
"status": "queued",
"_links": {
"result": "/tasks/tsk_…/result"
}
}The API is asynchronous: the call returns a task_id immediately and the result arrives by webhook. Polling is capped at 1 req/s per task.
Pricing
Published price — no tokens, no invented credits. A failed task is never charged.
Errors
| HTTP | Code | Meaning |
|---|---|---|
401 | unauthorized | Missing or invalid API key. |
402 | insufficient_balance | Your balance doesn't cover the task price. |
404 | unknown_type | That task type doesn't exist. |
429 | rate_limited | Too many requests. Use the webhook instead of polling. |
Technical notes
Fewer than two rails or empty text fail with invalid_input.
How the rail-fence zigzag works and what the response contains
Send JSON with required string field text set to any non-empty message and required integer field rails set to at least two; aliases input, value, s, and message work for the text body, while key, n, and num_rails alias the rail count when older forms reuse those names. On success the response includes result with the transformed text, mode normalized to encrypt or decrypt, rails echoing the integer used, length equal to the input character count, and method equal to rail_fence so clients can branch without sniffing shapes. Identical inputs always produce identical JSON because the path is pure index arithmetic with no network, no random seed, and no wall clock.
Validation rules, rails limits, and common pitfalls
Missing text, non-string text, or an empty string all raise invalid_input and are not charged on the prepaid API after the engine rejects the request. Missing rails, non-integer rails, or a rail count below two also fail with invalid_input; the product promise is a real multi-rail zigzag, and a single rail would leave the message unchanged. Rails above two hundred fifty-six are rejected to keep pattern construction bounded inside a Worker isolate item call.
API usage, pricing, classroom workflows, and practical limits
Use the prepaid KIT API when a backend, serverless function, tutoring platform, or SDK needs a stable contract under balance at $0.002 per successful item; validation failures never consume balance after rejection. A typical classroom workflow writes a short sentence on paper rails, compares student answers to the API result, then decrypts to show that transposition is invertible when the key is the rail count. Keep payloads to ordinary text sizes appropriate for a Worker isolate item call; this tool is for messages and snippets, not multi-megabyte binary blobs.
Integration use cases
Pipeline and CI fixtures
Scripts and continuous-integration tests pin deterministic rail-fence outputs so accidental pattern or rail-boundary bugs fail fast.