codebook.transposition

This module defines a number of classical transposition ciphers.

It features simple ciphers like the rail_fence and the scytale, as well as the fractionating transposition cipher adfgvx.

 1"""
 2This module defines a number of classical transposition ciphers.
 3
 4It features simple ciphers like the `rail_fence` and the `scytale`,
 5as well as the fractionating transposition cipher `adfgvx`.
 6"""
 7import collections
 8import itertools
 9
10from codebook.utils import codegroup, validate_key, validate_plaintext
11
12
13@codegroup
14def rail_fence(plaintext: str, *, n: int) -> str:
15    """Rail Fence cipher (page 8)
16
17    - `plaintext` is the message to be encrypted
18    - `n` is the number of rails to use
19    """
20    plaintext = validate_plaintext(plaintext)
21
22    seq = [""] * n
23    zigzag = itertools.cycle(itertools.chain(range(n - 1), range(n - 1, 0, -1)))
24    for i, c in zip(zigzag, plaintext):
25        seq[i] += c
26
27    return "".join(seq).upper()
28
29
30@codegroup
31def scytale(plaintext: str, *, diameter: int) -> str:
32    """Scytale cipher (page 8)
33
34    - `plaintext` is the message to be encrypted
35    - `diameter` is the number of letters that *can fit around the rod's circumference*
36    """
37    plaintext = validate_plaintext(plaintext)
38
39    d, m = divmod(len(plaintext), diameter)
40    rows = []
41    for i in range(diameter):
42        offset = i if m > i else m
43        start = i * d + offset
44        end = start + d + m - offset
45        rows.append(plaintext[start:end])
46
47    seq = []
48    for t in itertools.zip_longest(*rows):
49        seq.append("".join(c or "" for c in t))
50
51    return "".join(seq).upper()
52
53
54@codegroup
55def adfgvx(plaintext: str, *, key: str) -> str:
56    """ADFGVX cipher (Appendix F, page 374)
57
58    - `plaintext` is the message to be encrypted
59    - `key` is the keyword or keyphrase used for the transposition stage of the cipher
60
61    The ADFGVX cipher uses two different keys: a 36 symbol alphabet to encode
62    the 6x6 grid, and a keyword/keyphrase for the transposition stage.
63    For simplicity, the grid's values are hardcoded with the book's example:
64
65    |       | **A** | **D** | **F** | **G** | **V** | **X** |
66    | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
67    | **A** |   8   |   p   |   3   |   d   |   1   |   n   |
68    | **D** |   l   |   t   |   4   |   o   |   a   |   h   |
69    | **F** |   7   |   k   |   b   |   c   |   5   |   z   |
70    | **G** |   j   |   u   |   6   |   w   |   g   |   m   |
71    | **V** |   x   |   s   |   v   |   i   |   r   |   2   |
72    | **X** |   9   |   e   |   y   |   0   |   f   |   q   |
73    """
74    plaintext = validate_plaintext(plaintext)
75    key = validate_key(key)
76
77    lookup = {}
78    for i, c in enumerate("8p3d1nlt4oah7kbc5zju6wgmxsvir29ey0fq"):
79        y, x = divmod(i, 6)
80        lookup[ord(c)] = "ADFGVX"[y] + "ADFGVX"[x]
81
82    stage_one = plaintext.translate(lookup)
83
84    columns = collections.defaultdict(list)
85    for k, c in zip(itertools.cycle(key), stage_one):
86        columns[k].append(c)
87
88    seq = itertools.chain(*[columns[k] for k in sorted(key)])
89    return "".join(seq)
@codegroup
def rail_fence(plaintext: str, *, n: int) -> str:
14@codegroup
15def rail_fence(plaintext: str, *, n: int) -> str:
16    """Rail Fence cipher (page 8)
17
18    - `plaintext` is the message to be encrypted
19    - `n` is the number of rails to use
20    """
21    plaintext = validate_plaintext(plaintext)
22
23    seq = [""] * n
24    zigzag = itertools.cycle(itertools.chain(range(n - 1), range(n - 1, 0, -1)))
25    for i, c in zip(zigzag, plaintext):
26        seq[i] += c
27
28    return "".join(seq).upper()

Rail Fence cipher (page 8)

  • plaintext is the message to be encrypted
  • n is the number of rails to use
@codegroup
def scytale(plaintext: str, *, diameter: int) -> str:
31@codegroup
32def scytale(plaintext: str, *, diameter: int) -> str:
33    """Scytale cipher (page 8)
34
35    - `plaintext` is the message to be encrypted
36    - `diameter` is the number of letters that *can fit around the rod's circumference*
37    """
38    plaintext = validate_plaintext(plaintext)
39
40    d, m = divmod(len(plaintext), diameter)
41    rows = []
42    for i in range(diameter):
43        offset = i if m > i else m
44        start = i * d + offset
45        end = start + d + m - offset
46        rows.append(plaintext[start:end])
47
48    seq = []
49    for t in itertools.zip_longest(*rows):
50        seq.append("".join(c or "" for c in t))
51
52    return "".join(seq).upper()

Scytale cipher (page 8)

  • plaintext is the message to be encrypted
  • diameter is the number of letters that can fit around the rod's circumference
@codegroup
def adfgvx(plaintext: str, *, key: str) -> str:
55@codegroup
56def adfgvx(plaintext: str, *, key: str) -> str:
57    """ADFGVX cipher (Appendix F, page 374)
58
59    - `plaintext` is the message to be encrypted
60    - `key` is the keyword or keyphrase used for the transposition stage of the cipher
61
62    The ADFGVX cipher uses two different keys: a 36 symbol alphabet to encode
63    the 6x6 grid, and a keyword/keyphrase for the transposition stage.
64    For simplicity, the grid's values are hardcoded with the book's example:
65
66    |       | **A** | **D** | **F** | **G** | **V** | **X** |
67    | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
68    | **A** |   8   |   p   |   3   |   d   |   1   |   n   |
69    | **D** |   l   |   t   |   4   |   o   |   a   |   h   |
70    | **F** |   7   |   k   |   b   |   c   |   5   |   z   |
71    | **G** |   j   |   u   |   6   |   w   |   g   |   m   |
72    | **V** |   x   |   s   |   v   |   i   |   r   |   2   |
73    | **X** |   9   |   e   |   y   |   0   |   f   |   q   |
74    """
75    plaintext = validate_plaintext(plaintext)
76    key = validate_key(key)
77
78    lookup = {}
79    for i, c in enumerate("8p3d1nlt4oah7kbc5zju6wgmxsvir29ey0fq"):
80        y, x = divmod(i, 6)
81        lookup[ord(c)] = "ADFGVX"[y] + "ADFGVX"[x]
82
83    stage_one = plaintext.translate(lookup)
84
85    columns = collections.defaultdict(list)
86    for k, c in zip(itertools.cycle(key), stage_one):
87        columns[k].append(c)
88
89    seq = itertools.chain(*[columns[k] for k in sorted(key)])
90    return "".join(seq)

ADFGVX cipher (Appendix F, page 374)

  • plaintext is the message to be encrypted
  • key is the keyword or keyphrase used for the transposition stage of the cipher

The ADFGVX cipher uses two different keys: a 36 symbol alphabet to encode the 6x6 grid, and a keyword/keyphrase for the transposition stage. For simplicity, the grid's values are hardcoded with the book's example:

A D F G V X
A 8 p 3 d 1 n
D l t 4 o a h
F 7 k b c 5 z
G j u 6 w g m
V x s v i r 2
X 9 e y 0 f q