Bifid cipher

You are encouraged to solve this task according to the task description, using any language you may know.
- Description
The Bifid cipher is a polygraphic substitution cipher which was invented by Félix Delastelle in around 1901. It uses a 5 x 5 Polybius square combined with transposition and fractionation to encrypt a message. Any 5 x 5 Polybius square can be used but, as it only has 25 cells and there are 26 letters of the (English) alphabet, one cell needs to represent two letters - I and J being a common choice.
- Operation
Suppose we want to encrypt the message "ATTACKATDAWN".
We use this archetypal Polybius square where I and J share the same position.
x/y 1 2 3 4 5 ------------- 1 | A B C D E 2 | F G H I K 3 | L M N O P 4 | Q R S T U 5 | V W X Y Z
The message is first converted to its x, y coordinates, but they are written vertically beneath.
A T T A C K A T D A W N 1 4 4 1 1 2 1 4 1 1 5 3 1 4 4 1 3 5 1 4 4 1 2 3
They are then arranged in a row.
1 4 4 1 1 2 1 4 1 1 5 3 1 4 4 1 3 5 1 4 4 1 2 3
Finally, they are divided up into pairs which are used to look up the encrypted letters in the square.
14 41 12 14 11 53 14 41 35 14 41 23 D Q B D A X D Q P D Q H
The encrypted message is therefore "DQBDAXDQPDQH".
Decryption can be achieved by simply reversing these steps.
- Task
Write routines in your language to encrypt and descrypt a message using the Bifid cipher.
Use them to verify (including subsequent decryption):
1. The above example.
2. The example in the Wikipedia article using the message and Polybius square therein.
3. The above example but using the Polybius square in the Wikipedia article to illustrate that it doesn't matter which square you use as long, of course, as the same one is used for both encryption and decryption.
In addition, encrypt and decrypt the message "The invasion will start on the first of January" using any Polybius square you like. Convert the message to upper case and ignore spaces.
- Bonus
Suggest a way in which the cipher could be modified so that ALL 26 letters can be uniquely encrypted.
- Related task
Ada
-- Bifid cipher
-- J. Carter 2023 May
-- The Bifid cipher is included as part of the PragmAda Reusable Components (https://github.com/jrcarter/PragmARC)
with Ada.Text_IO;
with PragmARC.Encryption.Bifid;
procedure Bifid_Test is
package Bifid renames PragmARC.Encryption.Bifid;
Key_1 : constant Bifid.Square_Layout := ("ABCDE", "FGHIK", "LMNOP", "QRSTU", "VWXYZ");
Key_2 : constant Bifid.Square_Layout := ("BGWKZ", "QPNDS", "IOAXE", "FCLUM", "THYVR");
Msg_1 : constant String := "ATTACKATDAWN";
Msg_2 : constant String := "FLEEATONCE";
Msg_3 : constant String := "THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY";
Crypt_1 : String (Msg_1'Range);
Crypt_2 : String (Msg_2'Range);
Crypt_3 : String (Msg_3'Range);
begin -- Bifid_Test
Crypt_1 := Bifid.Encrypt (Msg_1, Key_1);
Ada.Text_IO.Put_Line (Item => Msg_1 & " => " & Crypt_1 & " => " & Bifid.Decrypt (Crypt_1, Key_1) );
Crypt_2 := Bifid.Encrypt (Msg_2, Key_2);
Ada.Text_IO.Put_Line (Item => Msg_2 & " => " & Crypt_2 & " => " & Bifid.Decrypt (Crypt_2, Key_2) );
Crypt_1 := Bifid.Encrypt (Msg_1, Key_2);
Ada.Text_IO.Put_Line (Item => Msg_1 & " => " & Crypt_1 & " => " & Bifid.Decrypt (Crypt_1, Key_2) );
Crypt_3 := Bifid.Encrypt (Msg_3, Key_2);
Ada.Text_IO.Put_Line (Item => Msg_3 & " => " & Crypt_3 & " => " & Bifid.Decrypt (Crypt_3, Key_2) );
end Bifid_Test;
- Output:
ATTACKATDAWN => DQBDAXDQPDQH => ATTACKATDAWN FLEEATONCE => UAEOLWRINS => FLEEATONCE ATTACKATDAWN => EYFENGIWDILA => ATTACKATDAWN THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY => RASOAQXCYRORXESXADETSWLTNIATEGISBRGBALY => THEINVASIONWILLSTARTONTHEFIRSTOFIANUARY
AppleScript
(* The "square" here is only notional, the characters' coordinates being calculated
from their offsets in a linear text key. But a square key is accepted if the
signaller's careless enough to keep or transmit one in this form. *)
on bifidEncipher(message, square)
return transcipher(message, square, 1)
end bifidEncipher
on bifidDecipher(message, square)
return transcipher(message, square, 2)
end bifidDecipher
on transcipher(message, square, code) -- code: 1 = encipher, 2 = decipher.
-- Check and massage the input.
set message to join(message's words, "")
if (message contains ".") then set message to replaceText(".", "", message)
set square to join(join(square, "")'s words, "")
set squarea to (count square)
set sqrt to (squarea ^ 0.5) as integer
if (sqrt * sqrt ≠ squarea) then error "Invalid key."
ignoring case
if ((sqrt < 6) and (message contains "J")) then ¬
set message to replaceText("J", "I", message)
end ignoring
-- Calculate coordinates from the message characters' offsets in the "square" text
-- and either split and recombine them (when enciphering) or not (deciphering).
set list1 to {}
set list2 to {{}, list1}'s item code
set astid to AppleScript's text item delimiters
ignoring case and diacriticals
repeat with chr in message
set AppleScript's text item delimiters to chr
set |offset - 1| to (count square's first text item)
set list1's end to |offset - 1| div sqrt + 1
set list2's end to |offset - 1| mod sqrt + 1
end repeat
end ignoring
set AppleScript's text item delimiters to astid
set coords to {list1 & list2, list1}'s item code
-- Calculate new offsets from the appropriate numbers in the coords list
-- and get the characters offset by those amounts in the "square" text.
set chrs to {}
set nCoords to (count coords)
set indexDifference to {1, nCoords div 2}'s item code
repeat with i from 1 to (nCoords div code) by (3 - code)
set |offset| to ((coords's item i) - 1) * sqrt + (coords's item (i + indexDifference))
set chrs's end to square's character |offset|
end repeat
return join(chrs, "")
end transcipher
on join(lst, delim)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to delim
set txt to lst as text
set AppleScript's text item delimiters to astid
return txt
end join
on replaceText(searchText, replacement, mainText)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to searchText
set textItems to mainText's text items
set AppleScript's text item delimiters to replacement
set mainText to textItems as text
set AppleScript's text item delimiters to astid
return mainText
end replaceText
on task()
set output to {}
repeat with this in {{"Given example:", "ATTACKATDAWN", "ABCDEFGHIKLMNOPQRSTUVWXYZ"}, ¬
{"Wikipedia example:", "FLEEATONCE", {"BGWKZ", "QPNDS", "IOAXE", "FCLUM", "THYVR"}}, ¬
{"A 6 x 6 square allows all 36 letters & digits to be encoded:", ¬
"The invasion will start on the 1st of January", ¬
"THEQUI
9CKBR0
1OWNF8
7XJMP2
3SVLA6
5ZYDG4"}}
set {heading, message, square} to this
set encrypted to bifidEncipher(message, square)
set decrypted to bifidDecipher(encrypted, square)
set output's end to heading
set output's end to message & " --> " & encrypted & " --> " & decrypted
end repeat
return join(output, linefeed)
end task
task()
- Output:
"Given example:
ATTACKATDAWN --> DQBDAXDQPDQH --> ATTACKATDAWN
Wikipedia example:
FLEEATONCE --> UAEOLWRINS --> FLEEATONCE
A 6 x 6 square allows all 36 letters & digits to be encoded:
The invasion will start on the 1st of January --> TTFAEWUAU9WTE3WP1S5KDF0B8M9AH7KHHVLAV --> THEINVASIONWILLSTARTONTHE1STOFJANUARY"
BASIC
Applesoft BASIC
100 A$ = "ATTACKATDAWN": GOSUB 160"REPORT
110 K$ = "BGWKZQPNDSIOAXEFCLUMTHYVR"
120 A$ = "FLEEATONCE": GOSUB 160"REPORT
130 K$ = " .'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123"
140 A$ = "THE INVASION WILL START ON THE FIRST OF JANUARY 2023.": GOSUB 160"REPORT
150 END
REM REPORT
160 GOSUB 200"ENCRYPT
165 PRINT M$M$"FOR "W" X "W" POLYBIUS:":M$ = CHR$ (13): FOR I = 1 TO W: PRINT , MID$ (K$,(I - 1) * W + 1,W): NEXT
170 PRINT "ENCRYPTED: "E$
180 GOSUB 300"DECRYPT
190 PRINT "DECRYPTED: "U$;: RETURN
REM ENCRYPT A$ RETURNS E$
200 GOSUB 400:L = LEN (A$):E$ = "":U$ = "": IF NOT L THEN RETURN
210 FOR I = 1 TO L
220 C = ASC ( MID$ (A$,I,1)): IF X(C) AND Y(C) THEN U$ = U$ + CHR$ (C)
230 NEXT I
240 L = LEN (U$): IF NOT L THEN RETURN
250 FOR I = 1 TO L:C = ASC ( MID$ (U$,I,1)):A(I) = X(C):A(I + L) = Y(C): NEXT I
260 FOR I = 1 TO L * 2 STEP 2:E$ = E$ + MID$ (K$,(A(I) - 1) * W + A(I + 1),1): NEXT I
270 RETURN
REM DECRYPT E$ RETURNS U$
300 GOSUB 400:L = LEN (E$):U$ = "": IF NOT L THEN RETURN
310 FOR I = 1 TO L:C = ASC ( MID$ (E$,I)):B(I * 2 - 1) = X(C):B(I * 2) = Y(C): NEXT I
320 FOR I = 1 TO L:U$ = U$ + MID$ (K$,(B(I) - 1) * W + B(L + I),1): NEXT I
330 RETURN
REM POLYBIUS K$ RETURNS X(255),Y(255)
400 IF K$ = P$ AND LEN (K$) THEN RETURN
410 IF XY THEN FOR I = 0 TO 255:X(I) = 0:Y(I) = 0: NEXT I
420 IF NOT XY THEN DIM X(255),Y(255),A(512),B(512):XY = 1
430 IF K$ = "" THEN FOR I = 1 TO 25:K$ = K$ + CHR$ (I + 64 + (I > 9)): NEXT I
440 L = LEN (K$):W = INT ( SQR (L - 1) + 1):I = 1:N = 1:K = ASC ("0")
450 FOR X = 1 TO W
460 FOR Y = 1 TO W
470 C$ = MID$ (K$,I,1): IF C$ = "" THEN FOR C = K TO 255: IF X(C) THEN NEXT C: STOP
480 IF C$ = "" THEN C$ = CHR$ (C):K$ = K$ + C$:K = C + 1
490 C = ASC (C$):Y(C) = Y:X(C) = X:I = I + 1: IF C$ = "J" THEN N = 0
500 NEXT Y,X
510 IF N THEN Y( ASC ("J")) = Y( ASC ("I")):X( ASC ("J")) = X( ASC ("I"))
520 P$ = K$
530 RETURN
- Output:
FOR 5 X 5 POLYBIUS: ABCDE FGHIK LMNOP QRSTU VWXYZ ENCRYPTED: DQBDAXDQPDQH DECRYPTED: ATTACKATDAWN FOR 5 X 5 POLYBIUS: BGWKZ QPNDS IOAXE FCLUM THYVR ENCRYPTED: UAEOLWRINS DECRYPTED: FLEEATONCE FOR 6 X 6 POLYBIUS: .'ABC DEFGHI JKLMNO PQRSTU VWXYZ0 123456 ENCRYPTED: QDFVQLBFJSAPLAE.GS'DJMAV56BWCVS6VILAYNCVZDOMV3 T4M.2K DECRYPTED: THE INVASION WILL START ON THE FIRST OF JANUARY 2023.
C++
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string_view>
#include <unordered_map>
#include <vector>
typedef std::pair<int32_t, int32_t> point;
class Bifid {
public:
Bifid(const int32_t n, std::string_view text) {
if ( text.length() != n * n ) {
throw std::invalid_argument("Incorrect length of text");
}
grid.resize(n);
for ( uint64_t i = 0; i < grid.size(); i++ ) {
grid[i].resize(n);
}
int32_t row = 0;
int32_t col = 0;
for ( const char& ch : text ) {
grid[row][col] = ch;
coordinates[ch] = point(row, col);
col += 1;
if ( col == n ) {
col = 0;
row += 1;
}
}
if ( n == 5 ) {
coordinates['J'] = coordinates['I'];
}
}
std::string encrypt(std::string_view text) {
std::vector<int32_t> row_one, row_two;
for ( const char& ch : text ) {
point coordinate = coordinates[ch];
row_one.push_back(coordinate.first);
row_two.push_back(coordinate.second);
}
row_one.insert(row_one.end(), row_two.begin(), row_two.end());
std::string result;
for ( uint64_t i = 0; i < row_one.size() - 1; i += 2 ) {
result += grid[row_one[i]][row_one[i + 1]];
}
return result;
}
std::string decrypt(std::string_view text) {
std::vector<int32_t> row;
for ( const char& ch : text ) {
point coordinate = coordinates[ch];
row.push_back(coordinate.first);
row.push_back(coordinate.second);
}
const int middle = row.size() / 2;
std::vector<int32_t> row_one = { row.begin(), row.begin() + middle };
std::vector<int32_t> row_two = { row.begin() + middle, row.end() };
std::string result;
for ( int32_t i = 0; i < middle; i++ ) {
result += grid[row_one[i]][row_two[i]];
}
return result;
}
void display() const {
for ( const std::vector<char>& row : grid ) {
for ( const char& ch : row ) {
std::cout << ch << " ";
}
std::cout << std::endl;
}
}
private:
std::vector<std::vector<char>> grid;
std::unordered_map<char, point> coordinates;
};
void runTest(Bifid& bifid, std::string_view message) {
std::cout << "Using Polybius square:" << std::endl;
bifid.display();
std::cout << "Message: " << message << std::endl;
std::string encrypted = bifid.encrypt(message);
std::cout << "Encrypted: " << encrypted << std::endl;
std::string decrypted = bifid.decrypt(encrypted);
std::cout << "Decrypted: " << decrypted << std::endl;
std::cout << std::endl;
}
int main() {
const std::string_view message1 = "ATTACKATDAWN";
const std::string_view message2 = "FLEEATONCE";
const std::string_view message3 = "THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY";
Bifid bifid1(5, "ABCDEFGHIKLMNOPQRSTUVWXYZ");
Bifid bifid2(5, "BGWKZQPNDSIOAXEFCLUMTHYVR");
runTest(bifid1, message1);
runTest(bifid2, message2);
runTest(bifid2, message1);
runTest(bifid1, message2);
Bifid bifid3(6, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
runTest(bifid3, message3);
}
- Output:
Using Polybius square: A B C D E F G H I K L M N O P Q R S T U V W X Y Z Message: ATTACKATDAWN Encrypted: DQBDAXDQPDQH Decrypted: ATTACKATDAWN Using Polybius square: B G W K Z Q P N D S I O A X E F C L U M T H Y V R Message: FLEEATONCE Encrypted: UAEOLWRINS Decrypted: FLEEATONCE Using Polybius square: B G W K Z Q P N D S I O A X E F C L U M T H Y V R Message: ATTACKATDAWN Encrypted: EYFENGIWDILA Decrypted: ATTACKATDAWN Using Polybius square: A B C D E F G H I K L M N O P Q R S T U V W X Y Z Message: FLEEATONCE Encrypted: HADNAAZDSP Decrypted: FLEEATONCE Using Polybius square: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9 Message: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY Encrypted: TBPDIPHJSPOTAIVMGPCZKNSCN09BFIHK64I7BM4 Decrypted: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY
EasyLang
func$ crypt enc msg$ key$ .
key$[] = strchars key$
n = len msg$
for i to len msg$
c$ = substr msg$ i 1
for j to 25
if c$ = key$[j]
break 1
.
.
j -= 1
h[] &= j div 5
h[] &= j mod 5
.
if enc = 1
for i = 1 step 4 to 2 * n - 3
j = h[i] * 5 + h[i + 2] + 1
r$ &= key$[j]
.
for i = 2 step 4 to 2 * n - 2
j = h[i] * 5 + h[i + 2] + 1
r$ &= key$[j]
.
else
for i = 1 to n
j = h[i] * 5 + h[i + n] + 1
r$ &= key$[j]
.
.
return r$
.
func$ conv s$ .
for e$ in strchars s$
h = strcode e$
if h >= 97
h -= 32
.
if h >= 65 and h <= 91
if h = 74
h = 73
.
r$ &= strchar h
.
.
return r$
.
func$ encr msg$ key$ .
return crypt 1 conv msg$ key$
.
func$ decr msg$ key$ .
return crypt 0 msg$ key$
.
key$ = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
h$ = encr "ATTACKATDAWN" key$
print h$
print decr h$ key$
print ""
#
key$ = "BGWKZQPNDSIOAXEFCLUMTHYVR"
h$ = encr "FLEEATONCE" key$
print h$
print decr h$ key$
print ""
h$ = encr "ATTACKATDAWN" key$
print h$
print decr h$ key$
print ""
#
h$ = encr "The invasion will start on the first of January" key$
print h$
print decr h$ key$
J
Implementation:alpha=: a.{~65+i.26
normalize=: {{ rplc&'JI'(toupper y)([-.-.)alpha }}
bifid=: {{ m{~_2 (t&#.)\,|:(t,t=.%:#m)#:m i.y([-.-.)m }}
difib=: {{ m{~t#.|:(|.@$$,)(t,t=.%:#m)#:m i.y([-.-.)m }}
This is pretty much a literal implementation of the algorithm, except that our indices range from 0..4 instead of 1..5 (or, for letter indices, they range from 0..24 instead of 1..25).
Much of the implementation is about converting between a two digit base 5 representation and a single digit numeric representation. The rest is simple array manipulations to make the rest come out right.
Task examples:ref1=: ~.normalize alpha
ref2=: 'BGWKZQPNDSIOAXEFCLUMTHYVR'
ref3=: 'PLAYFIREXMBCDGHKNOQSTUVWZ'
ref1 bifid normalize 'attack at dawn'
DQBDAXDQPDQH
ref1 difib ref1 bifid normalize 'attack at dawn'
ATTACKATDAWN
ref2 bifid normalize 'flee at once'
UAEOLWRINS
ref2 difib ref2 bifid normalize 'flee at once'
FLEEATONCE
ref2 bifid normalize 'attack at dawn'
EYFENGIWDILA
ref2 difib ref2 bifid normalize 'attack at dawn'
ATTACKATDAWN
ref3 bifid normalize 'The invasion will start on the first of January'
VRSYXSIYTMQVIRSKISLPVLDTCKRTCAIVTMATCEX
ref3 difib ref3 bifid normalize 'The invasion will start on the first of January'
THEINVASIONWILLSTARTONTHEFIRSTOFIANUARY
(_81{.123{.a.)bifid 'The invasion will start on the first of January'
TgqhpqpqxzpxfqzoKqhxw/O3eWH53BYw`+Be8F1
(_81{.123{.a.)difib(_81{.123{.a.)bifid 'The invasion will start on the first of January'
TheinvasionwillstartonthefirstofJanuary
Java
import java.awt.Point;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public final class BifidCipher {
public static void main(String[] aArgs) {
final String message1 = "ATTACKATDAWN";
final String message2 = "FLEEATONCE";
final String message3 = "The invasion will start on the first of January".toUpperCase().replace(" ", "");
Bifid bifid1 = new Bifid(5, "ABCDEFGHIKLMNOPQRSTUVWXYZ");
Bifid bifid2 = new Bifid(5, "BGWKZQPNDSIOAXEFCLUMTHYVR");
runTest(bifid1, message1);
runTest(bifid2, message2);
runTest(bifid2, message1);
runTest(bifid1, message2);
Bifid bifid3 = new Bifid(6, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
runTest(bifid3, message3);
}
private static void runTest(Bifid aBifid, String aMessage) {
System.out.println("Using Polybius square:");
aBifid.display();
System.out.println("Message: " + aMessage);
String encrypted = aBifid.encrypt(aMessage);
System.out.println("Encrypted: " + encrypted);
String decrypted = aBifid.decrypt(encrypted);
System.out.println("Decrypted: " + decrypted);
System.out.println();
}
}
final class Bifid {
public Bifid(int aN, String aText) {
if ( aText.length() != aN * aN ) {
throw new IllegalArgumentException("Incorrect length of text");
}
grid = new char[aN][aN];
int row = 0;
int col = 0;
for ( char ch : aText.toCharArray() ) {
grid[row][col] = ch;
coordinates.put(ch, new Point(row, col) );
col += 1;
if ( col == aN ) {
col = 0;
row += 1;
}
}
if ( aN == 5 ) {
coordinates.put('J', coordinates.get('I'));
}
}
public String encrypt(String aText) {
List<Integer> rowOne = new ArrayList<Integer>();
List<Integer> rowTwo = new ArrayList<Integer>();
for ( char ch : aText.toCharArray() ) {
Point coordinate = coordinates.get(ch);
rowOne.add(coordinate.x);
rowTwo.add(coordinate.y);
}
rowOne.addAll(rowTwo);
StringBuilder result = new StringBuilder();
for ( int i = 0; i < rowOne.size() - 1; i += 2 ) {
result.append(grid[rowOne.get(i)][rowOne.get(i + 1)]);
}
return result.toString();
}
public String decrypt(String aText) {
List<Integer> row = new ArrayList<Integer>();
for ( char ch : aText.toCharArray() ) {
Point coordinate = coordinates.get(ch);
row.add(coordinate.x);
row.add(coordinate.y);
}
final int middle = row.size() / 2;
List<Integer> rowOne = row.subList(0, middle);
List<Integer> rowTwo = row.subList(middle, row.size());
StringBuilder result = new StringBuilder();
for ( int i = 0; i < middle; i++ ) {
result.append(grid[rowOne.get(i)][rowTwo.get(i)]);
}
return result.toString();
}
public void display() {
Arrays.stream(grid).forEach( row -> System.out.println(Arrays.toString(row)) );
}
private char[][] grid;
private Map<Character, Point> coordinates = new HashMap<Character, Point>();
}
- Output:
Using Polybius square: [A, B, C, D, E] [F, G, H, I, K] [L, M, N, O, P] [Q, R, S, T, U] [V, W, X, Y, Z] Message: ATTACKATDAWN Encrypted: DQBDAXDQPDQH Decrypted: ATTACKATDAWN Using Polybius square: [B, G, W, K, Z] [Q, P, N, D, S] [I, O, A, X, E] [F, C, L, U, M] [T, H, Y, V, R] Message: FLEEATONCE Encrypted: UAEOLWRINS Decrypted: FLEEATONCE Using Polybius square: [B, G, W, K, Z] [Q, P, N, D, S] [I, O, A, X, E] [F, C, L, U, M] [T, H, Y, V, R] Message: ATTACKATDAWN Encrypted: EYFENGIWDILA Decrypted: ATTACKATDAWN Using Polybius square: [A, B, C, D, E] [F, G, H, I, K] [L, M, N, O, P] [Q, R, S, T, U] [V, W, X, Y, Z] Message: FLEEATONCE Encrypted: HADNAAZDSP Decrypted: FLEEATONCE Using Polybius square: [A, B, C, D, E, F] [G, H, I, J, K, L] [M, N, O, P, Q, R] [S, T, U, V, W, X] [Y, Z, 0, 1, 2, 3] [4, 5, 6, 7, 8, 9] Message: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY Encrypted: TBPDIPHJSPOTAIVMGPCZKNSCN09BFIHK64I7BM4 Decrypted: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY
jq
Adapted from Wren
Also works with gojq, the Go implementation of jq.
Preliminaries
# _nwise is for gojq
def _nwise($n):
def n: if length <= $n then . else .[0:$n] , (.[$n:] | n) end;
n;
# Input: a string
# Output: a stream of strings
def chars: explode[] | [.] | implode;
Bifid
# Input: the message to be encrypted
def encrypt($polybius):
(ascii_upcase | gsub("J"; "I") ) as $m
| {rows: [], cols : [] }
| reduce ($m|chars) as $c (.;
($polybius|index($c)) as $ix
| if $ix
then .rows += [($ix/5)|floor + 1]
| .cols += [($ix%5) + 1]
else .
end )
| reduce (.rows + .cols | _nwise(2)) as $pair ("";
(($pair[0] - 1) * 5 + $pair[1] - 1) as $ix
| . + $polybius[$ix:$ix+1] ) ;
# Input: the message to be decrypted
def decrypt($polybius):
reduce chars as $c ( {rows: [], cols : [] };
($polybius|index($c)) as $ix
| .rows += [($ix/5)|floor + 1]
| .cols += [($ix%5) + 1] )
| ([.rows, .cols] | transpose | flatten) as $lines
| ($lines|length/2) as $count
| $lines[:$count] as $rows
| $lines[$count:] as $cols
| [$rows, $cols] as $d
| reduce range(0; $count) as $i ("";
(($rows[$i] - 1) * 5 + $cols[$i] - 1) as $ix
| . + $polybius[$ix:$ix+1] ) ;
def polys:
def p1: "ABCDEFGHIKLMNOPQRSTUVWXYZ";
def p2: "BGWKZQPNDSIOAXEFCLUMTHYVR";
def p3: "PLAYFIREXMBCDGHKNOQSTUVWZ";
[p1, p2, p2, p3];
def messages: [
"ATTACKATDAWN",
"FLEEATONCE",
"ATTACKATDAWN",
"The invasion will start on the first of January"
];
def task:
range(0; messages|length) as $i
| messages[$i]
| encrypt(polys[$i]) as $encrypted
| ($encrypted | decrypt(polys[$i] )) as $decrypted
| "Message : \(.)",
"Encrypted : \($encrypted)",
"Decrypted : \($decrypted)"
"" ;
task
- Output:
Same as for #Wren.
Julia
Using the Raku example's test messages.
polybius(text) = Char.(reshape(Int.(collect(text)), isqrt(length(text)), :)')
function encrypt(message, poly)
positions = [findall(==(c), poly)[1] for c in message]
numbers = vcat([c[1] for c in positions], [c[2] for c in positions])
return String([poly[numbers[i], numbers[i+1]] for i in 1:2:length(numbers)-1])
end
function decrypt(message, poly)
n = length(message)
positions = [findall(==(c), poly)[1] for c in message]
numbers = reduce(vcat, [[c[1], c[2]] for c in positions])
return String([poly[numbers[i], numbers[i+n]] for i in 1:n])
end
for (key, text) in [("ABCDEFGHIKLMNOPQRSTUVWXYZ", "ATTACKATDAWN"), ("BGWKZQPNDSIOAXEFCLUMTHYVR", "FLEEATONCE"),
([' '; '.'; 'A':'Z'; 'a':'z'; '0':'9'], "The invasion will start on the first of January 2023.")]
poly = polybius(key)
encrypted = encrypt(text, poly)
decrypted = decrypt(encrypted, poly)
println("Using polybius:")
display(poly)
println("\n Message: $text\n Encrypted: $encrypted\n Decrypted: $decrypted\n\n")
end
- Output:
Using polybius: 5×5 Matrix{Char}: 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z' Message: ATTACKATDAWN Encrypted: DQBDAXDQPDQH Decrypted: ATTACKATDAWN Using polybius: 5×5 Matrix{Char}: 'B' 'G' 'W' 'K' 'Z' 'Q' 'P' 'N' 'D' 'S' 'I' 'O' 'A' 'X' 'E' 'F' 'C' 'L' 'U' 'M' 'T' 'H' 'Y' 'V' 'R' Message: FLEEATONCE Encrypted: UAEOLWRINS Decrypted: FLEEATONCE Using polybius: 8×8 Matrix{Char}: ' ' '.' 'A' 'B' 'C' 'D' 'E' 'F' 'G' 'H' 'I' 'J' 'K' 'L' 'M' 'N' 'O' 'P' 'Q' 'R' 'S' 'T' 'U' 'V' 'W' 'X' 'Y' 'Z' 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' 'l' 'm' 'n' 'o' 'p' 'q' 'r' 's' 't' 'u' 'v' 'w' 'x' 'y' 'z' '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' Message: The invasion will start on the first of January 2023. Encrypted: SejxqrEierbmrDiCjrDeJsbu89DWCHkgGS9E6tAG5 Ks2PBfCq uH Decrypted: The invasion will start on the first of January 2023.
Kotlin
import kotlin.math.sqrt
data class Square(private val square: String) {
private val dim: Int =
sqrt(square.length.toDouble()).toInt()
fun encode(ch: Char): Pair<Int, Int> =
square.indexOf(ch).let { idx -> Pair(idx / dim, idx.mod(dim)) }
fun decode(pair: List<Int>): Char =
square[pair[0] * dim + pair[1]]
fun decode(row: Int, col: Int): Char =
square[row * dim + col]
}
class Bifid(private val square: Square) {
fun encrypt(str: String): String {
fun expandAndScatter(str: String): IntArray {
val buffer = IntArray(str.length * 2)
str.forEachIndexed { i, ch ->
with(square.encode(ch)) {
buffer[i] = first
buffer[str.length + i] = second
}
}
return buffer
}
val buffer = expandAndScatter(str)
val characters: List<Char> = buffer.asIterable()
.windowed(size = 2, step = 2)
.map { square.decode(it) }
return String(characters.toCharArray())
}
fun decrypt(str: String): String {
fun expand(str: String): IntArray {
val buffer = IntArray(str.length * 2)
for (i in buffer.indices step 2) {
with(square.encode(str[i / 2])) {
buffer[i] = first
buffer[1 + i] = second
}
}
return buffer
}
val buffer = expand(str)
val characters = str.toCharArray()
for (i in characters.indices) {
characters[i] = square.decode(buffer[i], buffer[characters.size + i])
}
return String(characters)
}
}
fun main() {
with (Bifid(Square("ABCDEFGHIKLMNOPQRSTUVWXYZ"))) {
println("\n### ABC... 5x5")
encrypt("ATTACKATDAWN").also { println("ATTACKATDAWN -> $it") }
decrypt("DQBDAXDQPDQH").also { println("DQBDAXDQPDQH -> $it") }
}
with(Bifid(Square("BGWKZQPNDSIOAXEFCLUMTHYVR"))) {
println("\n### BGW... 5x5")
encrypt("FLEEATONCE").also { println("FLEEATONCE -> $it") }
decrypt("UAEOLWRINS").also { println("UAEOLWRINS -> $it") }
encrypt("ATTACKATDAWN").also { println("ATTACKATDAWN -> $it") }
decrypt("EYFENGIWDILA").also { println("EYFENGIWDILA -> $it") }
}
with(Bifid(Square("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"))) {
println("\n### ABC... 6x6")
encrypt("THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY").also { println("$it") }
decrypt("TBPDIPHJSPOTAIVMGPCZKNSCN09BFIHK64I7BM4").also { println("$it") }
}
}
- Output:
### ABC... 5x5 ATTACKATDAWN -> DQBDAXDQPDQH DQBDAXDQPDQH -> ATTACKATDAWN ### BGW... 5x5 FLEEATONCE -> UAEOLWRINS UAEOLWRINS -> FLEEATONCE ATTACKATDAWN -> EYFENGIWDILA EYFENGIWDILA -> ATTACKATDAWN ### ABC... 6x6 TBPDIPHJSPOTAIVMGPCZKNSCN09BFIHK64I7BM4 THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY
Lua
The "In addition" task and the bonus task are both solved using a 6x6 Polybius square containing every captial letter and all ten digits.
I refer to this combined solution as 'Task 4'.
function transcipher (cipher, message, decipher)
local message = message:gsub("%s+", ""):upper()
local xStr, yStr, s, char = "", "", ""
for pos = 1, #message do
char = message:sub(pos, pos)
for x = 1, #cipher do
for y = 1, #cipher[x] do
if cipher[x][y] == char then
s = s .. x .. y
xStr = xStr .. x
yStr = yStr .. y
end
end
end
end
if decipher then
xStr, yStr = s:sub(1, #s/2), s:sub(#s/2 + 1, #s)
else
s = xStr .. yStr
end
local result, x, y = ""
local limit = decipher and #s/2 or #s
local step = decipher and 1 or 2
for pos = 1, limit, step do
x = tonumber(s:sub(pos, pos))
y = decipher and
tonumber(s:sub(pos + #s/2, pos + #s/2)) or
tonumber(s:sub(pos + 1, pos + 1))
result = result .. cipher[x][y]
end
return result
end
local RCbifid = {
{"A", "B", "C", "D", "E"},
{"F", "G", "H", "I", "K"},
{"L", "M", "N", "O", "P"},
{"Q", "R", "S", "T", "U"},
{"V", "W", "X", "Y", "Z"}
}
local wikibifid = {
{"B", "G", "W", "K", "Z"},
{"Q", "P", "N", "D", "S"},
{"I", "O", "A", "X", "E"},
{"F", "C", "L", "U", "M"},
{"T", "H", "Y", "V", "R"}
}
local mybifid = {
{"A", "B", "C", "D", "E", "F"},
{"G", "H", "I", "J", "K", "L"},
{"M", "N", "O", "P", "Q", "R"},
{"S", "T", "U", "V", "W", "X"},
{"Y", "Z", "1", "2", "3", "4"},
{"5", "6", "7", "8", "9", "0"}
}
local testCases = {
{RCbifid, "ATTACKATDAWN"},
{wikibifid, "FLEEATONCE"},
{wikibifid, "ATTACKATDAWN",},
{mybifid, "The invasion will start on the first of January"}
}
local msg
for task, case in pairs(testCases) do
print("\nTask " .. task)
msg = transcipher(case[1], case[2])
print("Encoded message: " .. msg)
msg = transcipher(case[1], msg, true)
print("Decoded message: " .. msg)
end
- Output:
Task 1 Encoded message: DQBDAXDQPDQH Decoded message: ATTACKATDAWN Task 2 Encoded message: UAEOLWRINS Decoded message: FLEEATONCE Task 3 Encoded message: EYFENGIWDILA Decoded message: ATTACKATDAWN Task 4 Encoded message: TBPDIPHJSPOTAIVMGPCZKNSCN10BFIHK75I8BM5 Decoded message: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY
Nim
To add the letter J, we use a 6x6 Polybius square containing the 26 letters and the 10 digits. To build the square, we use a generic type parameterized with the side length.
import std/[sequtils, strutils, tables]
# Description of a Bifid cipher.
type Bifid[N: static Positive] = object
grid: array[1..N, array[1..N, char]]
coords: Table[char, (int, int)]
proc initBifid(N: static Positive; text: string): Bifid[N] =
# Initialize a Bifid cipher.
assert text.len == N * N
var row, col = 1
for c in text:
result.grid[row][col] = c
result.coords[c] = (row, col)
inc col
if col > N:
col = 1
inc row
if N == 5:
result.coords['J'] = result.coords['I']
func encrypt(bifid: Bifid; text: string): string =
## Encrypt "text" using the given cipher.
var row1, row2: seq[int]
for ch in text:
let coords = bifid.coords[ch]
row1.add coords[0]
row2.add coords[1]
let row = row1 & row2
for i in countup(0, row.high, 2):
result.add bifid.grid[row[i]][row[i+1]]
func decrypt(bifid: Bifid; text: string): string =
## Decrypt "text" using the given cipher.
var row: seq[int]
for ch in text:
let coords = bifid.coords[ch]
row.add [coords[0], coords[1]]
let m = row.len shr 1
let row1 = row[0..<m]
let row2 = row[m..^1]
for i in 0..<m:
result.add bifid.grid[row1[i]][row2[i]]
func `$`(bifid: Bifid): string =
## Display the Polybius square of a Bifid cipher.
result = " " & toSeq(1..bifid.N).join(" ") & '\n'
for row in 1..bifid.N:
result.add alignLeft($row, 2) & bifid.grid[row].join(" ") & '\n'
proc runTest(bifid: Bifid; message: string) =
## Run the test with given cipher and message.
echo "Using Polybius square:"
echo bifid
echo "Message: ", message
let encrypted = bifid.encrypt(message)
echo "Encrypted: ", encrypted
let decrypted = bifid.decrypt(encrypted)
echo "Decrypted: ", decrypted
echo("\n─────────────────────────")
const
Message1 = "ATTACKATDAWN"
Message2 = "FLEEATONCE"
Message3 = "The invasion will start on the first of January".toUpperAscii.replace(" ")
# Using 5x5 Polybius squares.
const
Bifid1 = initBifid(5, "ABCDEFGHIKLMNOPQRSTUVWXYZ")
Bifid2 = initBifid(5, "BGWKZQPNDSIOAXEFCLUMTHYVR")
for (bifid, message) in [(Bifid1, Message1), (Bifid2, Message2),
(Bifid2, Message1), (Bifid1, Message3)]:
runTest(bifid, message)
# Using a 6x6 Polybius square with 26 letters and 10 digits.
const Bifid3 = initBifid(6, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
runTest(Bifid3, Message3)
- Output:
Using Polybius square: 1 2 3 4 5 1 A B C D E 2 F G H I K 3 L M N O P 4 Q R S T U 5 V W X Y Z Message: ATTACKATDAWN Encrypted: DQBDAXDQPDQH Decrypted: ATTACKATDAWN ───────────────────────── Using Polybius square: 1 2 3 4 5 1 B G W K Z 2 Q P N D S 3 I O A X E 4 F C L U M 5 T H Y V R Message: FLEEATONCE Encrypted: UAEOLWRINS Decrypted: FLEEATONCE ───────────────────────── Using Polybius square: 1 2 3 4 5 1 B G W K Z 2 Q P N D S 3 I O A X E 4 F C L U M 5 T H Y V R Message: ATTACKATDAWN Encrypted: EYFENGIWDILA Decrypted: ATTACKATDAWN ───────────────────────── Using Polybius square: 1 2 3 4 5 1 A B C D E 2 F G H I K 3 L M N O P 4 Q R S T U 5 V W X Y Z Message: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY Encrypted: RBPDHPHOQTNRBITMFODYPSAOSIAOBTOPDHTDCVI Decrypted: THEINVASIONWILLSTARTONTHEFIRSTOFIANUARY ───────────────────────── Using Polybius square: 1 2 3 4 5 6 1 A B C D E F 2 G H I J K L 3 M N O P Q R 4 S T U V W X 5 Y Z 0 1 2 3 6 4 5 6 7 8 9 Message: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY Encrypted: TBPDIPHJSPOTAIVMGPCZKNSCN09BFIHK64I7BM4 Decrypted: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY ─────────────────────────
Perl
use v5.36;
use builtin <indexed floor>;
use experimental qw(builtin for_list);
use List::Util 'max';
sub table ($c, @V) { my $t = $c * (my $w = 2 + length max map { length } @V); ( sprintf( ('%'.$w.'s')x@V, @V) ) =~ s/.{1,$t}\K/\n/gr }
sub polybius ($text) {
my %p;
my $n = floor sqrt length $text;
for my($k,$v) (indexed split '', $text) {
$p{$v} = join ' ', $k%$n, int $k/$n
}
%p;
}
sub encrypt ($text, %P) {
my(%I, @c, $encrypted);
for my($k,$v) (%P) { $I{$v} = $k }
for my ($n,$char) (indexed split '', ($text =~ s/\s//gr)) {
for my($m,$i) (indexed split ' ', $P{$char}) { $c[$m][$n] = $i }
}
for my($i,$j) ($c[1]->@*, $c[0]->@*) { $encrypted .= $I{"$j $i"} }
$encrypted
}
sub decrypt ($text, %P) {
my($decrypted, $l, %I, @c) = ('', length($text));
for my($k,$v) (%P) { $I{$v} = $k }
for (split '', $text) {
for my($i,$j) (split ' ', $P{$_}) { unshift @c, $i, $j }
}
substr $decrypted, 0, 0, $I{ "$c[$_] $c[$_+$l]" } for 0 .. $l-1;
$decrypted;
}
for my($polybius,$message) (
join('','A'..'Z') =~ s/J//r, 'ATTACK AT DAWN',
'BGWKZQPNDSIOAXEFCLUMTHYVR', 'FLEE AT ONCE',
join('','_.', 'A'..'Z', 'a'..'z', '0'..'9'), 'The_invasion_will_start_on_the_first_of_January_2023.',
) {
my %Ptable = polybius $polybius;
say "\nUsing polybius:\n" . table sqrt length $polybius, split '', $polybius;
say 'Message : ' . $message;
say 'Encrypted : ' . (my $encrypted = encrypt $message, %Ptable);
say 'Decrypted : ' . decrypt $encrypted, %Ptable;
}
- Output:
Using polybius: A B C D E F G H I K L M N O P Q R S T U V W X Y Z Message : ATTACK AT DAWN Encrypted : DQBDAXDQPDQH Decrypted : ATTACKATDAWN Using polybius: B G W K Z Q P N D S I O A X E F C L U M T H Y V R Message : FLEE AT ONCE Encrypted : UAEOLWRINS Decrypted : FLEEATONCE Using polybius: _ . A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9 Message : The_invasion_will_start_on_the_first_of_January_2023. Encrypted : SejxqrEierbmrDiCjrDeJsbu89DWCHkgGS9E6tAG5_Ks2PBfCq_uH Decrypted : The_invasion_will_start_on_the_first_of_January_2023.
Phix
with javascript_semantics enum encrypt,decrypt function bifid(string msg, polybius, sequence p, integer n, ed) string res = "" sequence dx = {}, dy = {}, dxy for i=1 to length(msg) do integer k = find(msg[i],polybius)-1 dx &= floor(k/n)+1 dy &= remainder(k,n)+1 end for if ed=encrypt then dxy = split_by(dx&dy,2) else -- "simply reversing these steps" - yeah, right... dxy = columnize(split_by(flatten(columnize({dx,dy})),length(dx))) end if for i=1 to length(dxy) do integer {x,y} = dxy[i] res &= p[x][y] end for return res end function procedure test(string msg, polybius) integer n = sqrt(length(polybius)) if n=5 then msg = substitute(upper(msg),"J","I") end if sequence p = split_by(polybius,n) string enc = bifid(msg,polybius,p,n,encrypt), dec = bifid(enc,polybius,p,n,decrypt) printf(1,"For %dx%d polybius %s\n\n",{n,n,join(p,"\n ")}) printf(1,"Message : %s\nEncrypted: %s\nDecrypted: %s\n\n",{msg,enc,dec}) end procedure constant polybii = {tagstart('A','J'-'A')&tagstart('K','Z'-'J'), "BGWKZQPNDSIOAXEFCLUMTHYVR", " ."&tagstart('A',26)&tagstart('a',26)&tagstart('0',10)}, messages = {"ATTACKATDAWN","FLEEATONCE", "The invasion will start on the first of January 2023."} for t=1 to 3 do test(messages[t],polybii[t]) end for
- Output:
For 5x5 polybius ABCDE FGHIK LMNOP QRSTU VWXYZ Message : ATTACKATDAWN Encrypted: DQBDAXDQPDQH Decrypted: ATTACKATDAWN For 5x5 polybius BGWKZ QPNDS IOAXE FCLUM THYVR Message : FLEEATONCE Encrypted: UAEOLWRINS Decrypted: FLEEATONCE For 8x8 polybius .ABCDEF GHIJKLMN OPQRSTUV WXYZabcd efghijkl mnopqrst uvwxyz01 23456789 Message : The invasion will start on the first of January 2023. Encrypted: SejxqrEierbmrDiCjrDeJsbu89DWCHkgGS9E6tAG5 Ks2PBfCq uH Decrypted: The invasion will start on the first of January 2023.
Python
"""Bifid cipher. Requires Python >=3.7."""
import math
import pprint
import string
from itertools import chain
from itertools import zip_longest
from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import Tuple
from typing import TypeVar
T = TypeVar("T")
def group(it: Iterable[T], n: int) -> Iterator[Tuple[T, ...]]:
"""Return the input iterable split in to `n` equal chunks, padded with `None`."""
return zip_longest(*[iter(it)] * n)
Square = Tuple[Tuple[str, ...], ...]
def polybius_square(alphabet: str) -> Square:
"""Return the given alphabet as a tuple of tuples, representing a Polybius square."""
return tuple(group(alphabet, math.ceil(math.sqrt(len(alphabet)))))
def polybius_map(square: Square) -> Dict[str, Tuple[int, int]]:
"""Return a reverse lookup for the given Polybius square."""
return {
square[i][j]: (i + 1, j + 1)
for i in range(len(square))
for j in range(len(square))
}
def encrypt(message: str, square: Square) -> str:
"""Encrypt a plaintext message using a bifid cipher with the given Polybius square."""
_map = polybius_map(square)
return "".join(
square[x - 1][y - 1]
for x, y in group(
chain.from_iterable(zip(*(_map[c] for c in message if c in _map))),
2,
)
)
def decrypt(message: str, square: Square) -> str:
"""Decrypt a ciphertext message using a bifid cipher with the given Polybius square."""
_map = polybius_map(square)
return "".join(
square[x - 1][y - 1]
for x, y in zip(
*group(
chain.from_iterable((_map[c] for c in message if c in _map)),
len(message),
)
)
)
def normalize(message: str) -> str:
"""Normalize a message for the typical Polybius square."""
return message.upper().replace("J", "I")
TYPICAL_POLYBIUS_SQUARE = polybius_square(
alphabet="".join(c for c in string.ascii_uppercase if c != "J")
)
EXAMPLE_POLYBIUS_SQUARE = polybius_square(
alphabet="BGWKZQPNDSIOAXEFCLUMTHYVR",
)
def main() -> None:
test_cases = [
("ATTACKATDAWN", TYPICAL_POLYBIUS_SQUARE), # 1
("FLEEATONCE", EXAMPLE_POLYBIUS_SQUARE), # 2
("FLEEATONCE", TYPICAL_POLYBIUS_SQUARE), # 3
(
normalize("The invasion will start on the first of January"),
polybius_square(alphabet="PLAYFIREXMBCDGHKNOQSTUVWZ"),
),
(
"The invasion will start on the first of January".upper(),
polybius_square(alphabet=string.ascii_uppercase + string.digits),
),
]
for message, square in test_cases:
pprint.pprint(square)
print("Message :", message)
print("Encrypted:", encrypt(message, square))
print("Decrypted:", decrypt(encrypt(message, square), square))
print("")
if __name__ == "__main__":
main()
- Output:
(('A', 'B', 'C', 'D', 'E'), ('F', 'G', 'H', 'I', 'K'), ('L', 'M', 'N', 'O', 'P'), ('Q', 'R', 'S', 'T', 'U'), ('V', 'W', 'X', 'Y', 'Z')) Message : ATTACKATDAWN Encrypted: DQBDAXDQPDQH Decrypted: ATTACKATDAWN (('B', 'G', 'W', 'K', 'Z'), ('Q', 'P', 'N', 'D', 'S'), ('I', 'O', 'A', 'X', 'E'), ('F', 'C', 'L', 'U', 'M'), ('T', 'H', 'Y', 'V', 'R')) Message : FLEEATONCE Encrypted: UAEOLWRINS Decrypted: FLEEATONCE (('A', 'B', 'C', 'D', 'E'), ('F', 'G', 'H', 'I', 'K'), ('L', 'M', 'N', 'O', 'P'), ('Q', 'R', 'S', 'T', 'U'), ('V', 'W', 'X', 'Y', 'Z')) Message : FLEEATONCE Encrypted: HADNAAZDSP Decrypted: FLEEATONCE (('P', 'L', 'A', 'Y', 'F'), ('I', 'R', 'E', 'X', 'M'), ('B', 'C', 'D', 'G', 'H'), ('K', 'N', 'O', 'Q', 'S'), ('T', 'U', 'V', 'W', 'Z')) Message : THE INVASION WILL START ON THE FIRST OF IANUARY Encrypted: VRSYXSIYTMQVIRSKISLPVLDTCKRTCAIVTMATCEX Decrypted: THEINVASIONWILLSTARTONTHEFIRSTOFIANUARY (('A', 'B', 'C', 'D', 'E', 'F'), ('G', 'H', 'I', 'J', 'K', 'L'), ('M', 'N', 'O', 'P', 'Q', 'R'), ('S', 'T', 'U', 'V', 'W', 'X'), ('Y', 'Z', '0', '1', '2', '3'), ('4', '5', '6', '7', '8', '9')) Message : THE INVASION WILL START ON THE FIRST OF JANUARY Encrypted: TBPDIPHJSPOTAIVMGPCZKNSCN09BFIHK64I7BM4 Decrypted: THEINVASIONWILLSTARTONTHEFIRSTOFJANUARY
Quackery
transpose
is defined at Matrix transposition#Quackery.
[ $ "" swap
witheach
[ upper
dup char I > if [ 1 - ]
dup char A char Z
within iff
[ char A - join ]
else drop ] ] is ->0..24 ( $ --> [ )
[ $ "" swap
witheach
[ char A +
dup char I > if 1+
join ] ] is ->A..Z ( [ --> $ )
[ [] 5 times
[ dip ->0..24 join ] ] is makesquare ( $ $ $ $ $ --> [ )
[ dup witheach
[ i^ unrot poke ] ] is makeindex ( [ --> [ )
[ dup temp put
makeindex temp put
->0..24
[] swap witheach
[ temp share swap peek
5 /mod join
nested join ]
temp release
transpose
unpack join
[] swap
dup size 2 / times
[ 2 split dip
[ nested join ] ]
drop
$ "" swap
witheach
[ unpack swap 5 * +
temp share swap peek
join ]
->A..Z
temp release ] is encrypt ( $ [ --> $ )
[ dup temp put
makeindex temp put
->0..24
[] swap witheach
[ temp share swap peek
5 /mod join join ]
temp release
dup size 2 / split
2 pack
transpose
[] swap witheach
[ unpack swap 5 * +
temp share swap peek
join ]
->A..Z
temp release ] is decrypt ( $ [ --> $ )
[ $ "ABCDE"
$ "FGHIK"
$ "LMNOP"
$ "QRSTU"
$ "VWXYZ"
makesquare ] constant is tasksquare ( --> [ )
[ $ "BGWKZ"
$ "QPNDS"
$ "IOAXE"
$ "FCLUM"
$ "THYVR"
makesquare ] constant is wikisquare ( --> [ )
[ $ "QUACK"
$ "DEPTH"
$ "LYING"
$ "FORMS"
$ "BVWXZ"
makesquare ] constant is ducksquare ( --> [ )
say "Using tasksquare:" cr
$ "Attack at dawn." dup echo$ say " -> "
tasksquare encrypt dup echo$ say " -> "
tasksquare decrypt echo$
cr cr
say "Using wikisquare:" cr
$ "Flee at once." dup echo$ say " -> "
wikisquare encrypt dup echo$ say " -> "
wikisquare decrypt echo$
cr cr
say "Using ducksquare:" cr
$ "The invasion will start on the first of January." dup echo$ cr say " -> "
ducksquare encrypt dup echo$ cr say " -> "
ducksquare decrypt echo$
- Output:
Using tasksquare: Attack at dawn. -> DQBDAXDQPDQH -> ATTACKATDAWN Using wikisquare: Flee at once. -> UAEOLWRINS -> FLEEATONCE Using ducksquare: The invasion will start on the first of January. -> EPGCNGINDORETNOMLLCNVNPWTIQXIOMVAGOANPY -> THEINVASIONWILLSTARTONTHEFIRSTOFIANUARY
Raku
Technically incorrect as the third part doesn't "Convert ... to upper case and ignore spaces".
sub polybius ($text) {
my $n = $text.chars.sqrt.narrow;
$text.comb.kv.map: { $^v => ($^k % $n, $k div $n).join: ' ' }
}
sub encrypt ($message, %poly) {
%poly.invert.hash{(flat reverse [Z] %poly{$message.comb}».words).batch(2)».reverse».join: ' '}.join
}
sub decrypt ($message, %poly) {
%poly.invert.hash{reverse [Z] (reverse flat %poly{$message.comb}».words».reverse).batch($message.chars)}.join
}
for 'ABCDEFGHIKLMNOPQRSTUVWXYZ', 'ATTACKATDAWN',
'BGWKZQPNDSIOAXEFCLUMTHYVR', 'FLEEATONCE',
(flat '_', '.', 'A'..'Z', 'a'..'z', 0..9).pick(*).join, 'The invasion will start on the first of January 2023.'.subst(/' '/, '_', :g)
-> $polybius, $message {
my %polybius = polybius $polybius;
say "\nUsing polybius:\n\t" ~ $polybius.comb.batch($polybius.chars.sqrt.narrow).join: "\n\t";
say "\n Message : $message";
say "Encrypted : " ~ my $encrypted = encrypt $message, %polybius;
say "Decrypted : " ~ decrypt $encrypted, %polybius;
}
- Output:
Using polybius: A B C D E F G H I K L M N O P Q R S T U V W X Y Z Message : ATTACKATDAWN Encrypted : DQBDAXDQPDQH Decrypted : ATTACKATDAWN Using polybius: B G W K Z Q P N D S I O A X E F C L U M T H Y V R Message : FLEEATONCE Encrypted : UAEOLWRINS Decrypted : FLEEATONCE Using polybius: H c F T N 5 f i _ U k R B Z V W 3 G e v s w j x q S 2 8 y Q . O m 0 E d h D r u M p 7 Y 4 A 9 a t X l I 6 g b z J P n 1 K L C o Message : The_invasion_will_start_on_the_first_of_January_2023. Encrypted : NGiw3okfXj4XoVE_NjWcLK4Sy28EivKo3aeNiti3N3z6HCHno6Fkf Decrypted : The_invasion_will_start_on_the_first_of_January_2023.
Ruby
def cleanMsg(msg, square)
msg.upcase!
msg.delete!(' ')
msg.delete!('J') if square.index('J') == nil
end
def encrypt(msg, square)
cleanMsg msg, square
sq_size = (square.length ** 0.5).to_i
rows = [0] * msg.length
cols = [0] * msg.length
(0...msg.length).each do |i|
p = square.index(msg[i])
rows[i], cols[i] = p / sq_size, p % sq_size
end
result = ""
rows.concat(cols).each_slice(2) do |coord|
result += square[coord[0]*sq_size + coord[1]]
end
return result
end
def decrypt(msg, square)
msg.upcase!; square.upcase!
sq_size = (square.length ** 0.5).to_i
coords = []
result = ""
(0...msg.length).each do |i|
p = square.index(msg[i])
coords << p / sq_size
coords << p % sq_size
end
for i in (0...coords.length/2) do
row, col = coords[i], coords[i+coords.length/2]
result += square[row*sq_size + col]
end
return result
end
def printSquare(square)
sq_size = (square.length ** 0.5).to_i
(0..square.length).step(sq_size).each do |i|
print square[i...(i+sq_size)], "\n"
end
end
tests = [["ATTACKATDAWN" , "ABCDEFGHIKLMNOPQRSTUVWXYZ"],
["FLEEATONCE" , "BGWKZQPNDSIOAXEFCLUMTHYVR"],
["ATTACKATDAWN" , "ABCDEFGHIKLMNOPQRSTUVWXYZ"],
["the invasion will start on the first of january", "BGWKZQPNDSIOAXEFCLUMTHYVR"],
["THIS MESSAGE HAS NUMBERS 2023", "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"],
]
for test in tests
message = test[0]; square = test[1];
encrypted = encrypt(message, square)
decrypted = decrypt(encrypted, square)
puts "using the polybius:"
printSquare(square)
puts "the plain message:", message
puts "encrypted:", encrypted
puts "decrypted:", decrypted
puts "===================================================="
end
- Output:
using the polybius: ABCDE FGHIK LMNOP QRSTU VWXYZ the plain message: ATTACKATDAWN encrypted: DQBDAXDQPDQH decrypted: ATTACKATDAWN ==================================================== using the polybius: BGWKZ QPNDS IOAXE FCLUM THYVR the plain message: FLEEATONCE encrypted: UAEOLWRINS decrypted: FLEEATONCE ==================================================== using the polybius: ABCDE FGHIK LMNOP QRSTU VWXYZ the plain message: ATTACKATDAWN encrypted: DQBDAXDQPDQH decrypted: ATTACKATDAWN ==================================================== using the polybius: BGWKZ QPNDS IOAXE FCLUM THYVR the plain message: THEINVASIONWILLSTARTONTHEFIRSTOFANUARY encrypted: RASOAQXCYRORXESXOLRGTXEGAWEWTNGTZTQALY decrypted: THEINVASIONWILLSTARTONTHEFIRSTOFANUARY ==================================================== using the polybius: ABCDEF GHIJKL MNOPQR STUVWX YZ1234 567890 the plain message: THISMESSAGEHASNUMBERS2023 encrypted: TJMVBBDPMCW9ZIAYAEGBMK5XW decrypted: THISMESSAGEHASNUMBERS2023 ====================================================
Wren
One way of enabling all 26 letters to be encrypted uniquely would be to use a 6 x 6 Polybius square including the 10 digits. We could then encrypt text using numerals as well.
However, the following just uses the standard version of the cipher.
import "./str" for Str
import "./seq" for Lst
class Bifid {
static encrypt(polybius, message) {
message = Str.upper(message).replace("J", "I")
var rows = []
var cols = []
for (c in message) {
var ix = polybius.indexOf(c)
if (ix == -1) continue
rows.add((ix/5).floor + 1)
cols.add((ix%5) + 1)
}
var s = ""
for (pair in Lst.chunks(rows + cols, 2)) {
var ix = (pair[0] - 1) * 5 + pair[1] - 1
s = s + polybius[ix]
}
return s
}
static decrypt(polybius, message) {
var rows = []
var cols = []
for (c in message) {
var ix = polybius.indexOf(c)
rows.add((ix/5).floor + 1)
cols.add((ix%5) + 1)
}
var lines = Lst.flatten(Lst.zip(rows, cols))
var count = lines.count/2
rows = lines[0...count]
cols = lines[count..-1]
var s = ""
for (i in 0...count) {
var ix = (rows[i] - 1) * 5 + cols[i] - 1
s = s + polybius[ix]
}
return s
}
}
var poly1 = "ABCDEFGHIKLMNOPQRSTUVWXYZ"
var poly2 = "BGWKZQPNDSIOAXEFCLUMTHYVR"
var poly3 = "PLAYFIREXMBCDGHKNOQSTUVWZ"
var polys = [poly1, poly2, poly2, poly3]
var msg1 = "ATTACKATDAWN"
var msg2 = "FLEEATONCE"
var msg3 = "The invasion will start on the first of January"
var msgs = [msg1, msg2, msg1, msg3]
for (i in 0...msgs.count) {
var encrypted = Bifid.encrypt(polys[i], msgs[i])
var decrypted = Bifid.decrypt(polys[i], encrypted)
System.print("Message : %(msgs[i])")
System.print("Encrypted : %(encrypted)")
System.print("Decrypted : %(decrypted)")
if (i < msgs.count-1) System.print()
}
- Output:
Message : ATTACKATDAWN Encrypted : DQBDAXDQPDQH Decrypted : ATTACKATDAWN Message : FLEEATONCE Encrypted : UAEOLWRINS Decrypted : FLEEATONCE Message : ATTACKATDAWN Encrypted : EYFENGIWDILA Decrypted : ATTACKATDAWN Message : The invasion will start on the first of January Encrypted : VRSYXSIYTMQVIRSKISLPVLDTCKRTCAIVTMATCEX Decrypted : THEINVASIONWILLSTARTONTHEFIRSTOFIANUARY