Open this lesson in your favourite AI. It'll walk you through the why, explain the demo, and quiz you on the try-it list.
Transposition ciphers don't change which letters appear in the message — only their order. By itself this is weak (a cryptanalyst can recover the plaintext by anagramming and language statistics), but transposition is half of what makes modern block ciphers work. Shannon identified the two components every strong cipher needs: confusion (a complex relationship between key and ciphertext, achieved by substitution) and diffusion (each plaintext bit affecting many ciphertext bits, achieved by transposition). AES alternates ShiftRows and MixColumns (transposition / diffusion) with SubBytes (substitution / confusion) for ten rounds. So when you study a Rail Fence or columnar transposition, you are studying the half of AES that spreads information around — without it, even strong substitution would leak local plaintext structure.
A columnar transposition writes the plaintext into a grid row by row under a key that orders the columns, then reads off the columns in the order specified by the key. The Rail Fence cipher is a special case where the 'columns' are zigzag rails.
// main.go
package main
import (
"fmt"
"sort"
"strings"
)
func railFenceEncrypt(plaintext string, rails int) string {
plaintext = strings.ReplaceAll(plaintext, " ", "")
fence := make([][]byte, rails)
rail, step := 0, 1
for i := 0; i < len(plaintext); i++ {
fence[rail] = append(fence[rail], plaintext[i])
rail += step
if rail == 0 || rail == rails-1 {
step = -step
}
}
var sb strings.Builder
for _, r := range fence {
sb.Write(r)
}
return sb.String()
}
func columnarEncrypt(plaintext, key string) string {
pt := strings.ToUpper(strings.ReplaceAll(plaintext, " ", ""))
cols := len(key)
rows := (len(pt) + cols - 1) / cols
// Pad with X
for len(pt) < rows*cols {
pt += "X"
}
// Build column order sorted by key letter
order := make([]int, cols)
for i := range order {
order[i] = i
}
sort.Slice(order, func(a, b int) bool { return key[order[a]] < key[order[b]] })
var sb strings.Builder
for _, c := range order {
for r := 0; r < rows; r++ {
sb.WriteByte(pt[r*cols+c])
}
}
return sb.String()
}
func columnarDecrypt(ciphertext, key string) string {
cols := len(key)
rows := len(ciphertext) / cols
order := make([]int, cols)
for i := range order {
order[i] = i
}
sort.Slice(order, func(a, b int) bool { return key[order[a]] < key[order[b]] })
grid := make([][]byte, rows)
for i := range grid {
grid[i] = make([]byte, cols)
}
pos := 0
for _, c := range order {
for r := 0; r < rows; r++ {
grid[r][c] = ciphertext[pos]
pos++
}
}
var sb strings.Builder
for _, row := range grid {
sb.Write(row)
}
return sb.String()
}
func main() {
fmt.Println(railFenceEncrypt("WEAREDISCOVEREDFLEEATONCE", 3))
// WECRLTEERDSOEEFEAOCAIVDEN
ct := columnarEncrypt("WEAREDISCOVEREDFLEEATONCE", "ZEBRA")
fmt.Println(ct)
fmt.Println(columnarDecrypt(ct, "ZEBRA"))
}go run main.goUse these three in order. Each builds on the one before.
In one paragraph, explain what a transposition cipher is, how it differs from a substitution cipher, and why the letter frequencies of a transposition ciphertext match those of the plaintext.
Walk me through columnar transposition encryption and decryption step by step, including how the key determines the column read-order and how padding affects the inverse.
Explain Shannon's confusion-diffusion principle: why is transposition alone insufficient for security, why is substitution alone insufficient, and how does AES interleave the two across rounds to achieve both?