ADFGVX cipher
You are encouraged to solve this task according to the task description, using any language you may know.
- Description
The ADFGVX cipher was a manually applied field cipher used by the German Army during World War I. It was broken in 1918 by the French cryptanalyst Georges Painvin.
The workings of the cipher are described in the Wikipedia article, linked to above, and so will not be repeated here.
- Task
Write routines, functions etc. in your language to:
1. Encrypt suitable plaintext and decrypt the resulting cipher text using the ADFGVX cipher algorithm given a Polybius square (see 2. below) and a suitable key. For this purpose suitable means text consisting solely of ASCII upper case letters or digits.
2. Create a 6 x 6 Polybius square using a random combination of the letters A to Z and the digits 0 to 9 and then display it.
3. Given the number of letters (between 7 and 12 say) to use, create a key by selecting a suitable word at random from unixdict.txt and then display it. The word selected should be such that none of its characters are repeated.
Use these routines to create a Polybius square and a 9 letter key.
These should then be used to encrypt the plaintext: ATTACKAT1200AM and decrypt the resulting cipher text. Display here the results of both operations.
- Note
As it's unclear from the Wikipedia article how to handle a final row with fewer elements than the number of characters in the key, either of the methods mentioned in Columnar transposition may be used. In the case of the second method, it is also acceptable to fill any gaps after shuffling by moving elements to the left which makes decipherment harder.
11l
V adfgvx = ‘ADFGVX’
F encrypt(plainText, polybius, key)
V s = ‘’
L(ch) plainText
L(r) 6
L(c) 6
I polybius[r][c] == ch
s ‘’= :adfgvx[r]‘’:adfgvx[c]
DefaultDict[Char, String] cols
L(ch) s
cols[key[L.index % key.len]] ‘’= ch
V result = ‘’
L(k) sorted(cols.keys())
I !result.empty
result ‘’= ‘ ’
result ‘’= cols[k]
R result
F decrypt(cipherText, polybius, key)
V skey = sorted(key)
V cols = [‘’] * key.len
V idx = 0
L(col) cipherText.split(‘ ’)
cols[key.findi(skey[idx])] = col
idx++
V s = ‘’
L(i) 0 .< key.len
L(col) cols
I i < col.len
s ‘’= col[i]
V result = ‘’
L(i) (0 .< s.len - 1).step(2)
V r = :adfgvx.findi(s[i])
V c = :adfgvx.findi(s[i + 1])
result ‘’= polybius[r][c]
R result
V polybius = [[Char("\0")] * 6] * 6
V alphabet = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789’
random:shuffle(&alphabet)
L(r) 6
L(c) 6
polybius[r][c] = alphabet[6 * r + c]
print("6 x 6 Polybius square:\n")
print(‘ | A D F G V X’)
print(‘---------------’)
L(row) polybius
print(adfgvx[L.index]‘ | ’row.join(‘ ’))
V words = File(‘unixdict.txt’).read().split("\n").filter(w -> w.len == 9 & w.len == Set(Array(w)).len)
V key = random:choice(words).uppercase()
print("\nThe key is "key)
V PlainText = ‘ATTACKAT1200AM’
print("\nPlaintext : "PlainText)
V cipherText = encrypt(PlainText, polybius, key)
print("\nEncrypted : "cipherText)
V plainText = decrypt(cipherText, polybius, key)
print("\nDecrypted : "plainText)
- Output:
6 x 6 Polybius square: | A D F G V X --------------- A | X O F P D 6 D | V H C 4 0 Z F | J M K R U 5 G | I A 9 Y B W V | 3 L 2 1 N G X | Q T E 7 8 S The key is EXCURSION Plaintext : ATTACKAT1200AM Encrypted : XFD GFVD GDG DGF DVD XDD DXV DGV DFF Decrypted : ATTACKAT1200AM
ARM Assembly
/* ARM assembly Raspberry PI */
/* program adfgvx.s */
/* remark 1 : At each launch, the random values are identical.
To change them, modify the value of the seed (graine) */
/* remark 2 : this program not run in android with termux
because the call system stats is not find */
/************************************/
/* Constantes */
/************************************/
/* for constantes see task include a file in arm assembly */
.include "../constantes.inc"
.equ SIZE, 6
.equ SIZEC, SIZE * SIZE
.equ KEYSIZE, 9
.equ READ, 3
.equ WRITE, 4
.equ OPEN, 5
.equ CLOSE, 6
.equ FSTAT, 0x6C
.equ O_RDWR, 0x0002 @ open for reading and writing
/**********************************************/
/* structure de type stat : infos fichier */
/**********************************************/
.struct 0
Stat_dev_t: @ ID of device containing file
.struct Stat_dev_t + 4
Stat_ino_t: @ inode
.struct Stat_ino_t + 2
Stat_mode_t: @ File type and mode
.struct Stat_mode_t + 2
Stat_nlink_t: @ Number of hard links
.struct Stat_nlink_t + 2
Stat_uid_t: @ User ID of owner
.struct Stat_uid_t + 2
Stat_gid_t: @ Group ID of owner
.struct Stat_gid_t + 2
Stat_rdev_t: @ Device ID (if special file)
.struct Stat_rdev_t + 2
Stat_size_deb: @ la taille est sur 8 octets si gros fichiers
.struct Stat_size_deb + 4
Stat_size_t: @ Total size, in bytes
.struct Stat_size_t + 4
Stat_blksize_t: @ Block size for filesystem I/O
.struct Stat_blksize_t + 4
Stat_blkcnt_t: @ Number of 512B blocks allocated
.struct Stat_blkcnt_t + 4
Stat_atime: @ date et heure fichier
.struct Stat_atime + 8
Stat_mtime: @ date et heure modif fichier
.struct Stat_atime + 8
Stat_ctime: @ date et heure creation fichier
.struct Stat_atime + 8
Stat_Fin:
/*********************************/
/* Initialized data */
/*********************************/
.data
szText: .asciz "ATTACKAT1200AM"
//szText: .asciz "ABCDEFGHIJ"
szMessOpen: .asciz "File open error.\n"
szMessStat: .asciz "File information error.\n"
szMessRead: .asciz "File read error.\n"
szMessClose: .asciz "File close error.\n"
szMessDecryptText: .asciz "Decrypted text :\n"
szMessCryptText: .asciz "Encrypted text :\n"
szMessErrorChar: .asciz "Character text not Ok!\n"
szFileName: .asciz "unixdict.txt"
szMessPolybius: .asciz "6 x 6 Polybius square:\n"
szTitle: .asciz " | A D F G V X\n---------------\n"
szLine1: .asciz "A | \n"
szLine2: .asciz "D | \n"
szLine3: .asciz "F | \n"
szLine4: .asciz "G | \n"
szLine5: .asciz "V | \n"
szLine6: .asciz "X | \n"
szListCharCode: .asciz "ADFGVX"
szListChar: .asciz "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.equ LGLISTCHAR, . - szListChar - 1
szMessStart: .asciz "Program 32 bits start.\n"
szCarriageReturn: .asciz "\n"
.align 4
iGraine: .int 1234567 // random init
/*********************************/
/* UnInitialized data */
/*********************************/
.bss
sKeyWord: .skip 16
sKeyWordSorted: .skip 16
tabPolybius: .skip SIZE * SIZE + 4
sBuffer: .skip 1000
sBuffer1: .skip 1000
sBuffer2: .skip 1000
tabPosit: .skip 16
tabPositInv: .skip 16
/*********************************/
/* code section */
/*********************************/
.text
.global main
main: @ entry of program
ldr r0,iAdrszMessStart
bl affichageMess
bl createPolybius @ create 6*6 polybius
ldr r0,iAdrsKeyWord
bl generateKey @ generate key
cmp r0,#-1 @ file error ?
beq 100f
bl affichageMess @ display key
ldr r0,iAdrszCarriageReturn
bl affichageMess
ldr r0,iAdrszMessCryptText
bl affichageMess
ldr r0,iAdrszText @ text encrypt
ldr r1,iAdrtabPolybius
ldr r2,iAdrsKeyWord
ldr r3,iAdrsBuffer @ result buffer
bl encryption
cmp r0,#-1 @ error if unknow character in text
beq 100f
bl affichageMess @ display text encrypted
ldr r0,iAdrszCarriageReturn
bl affichageMess
ldr r0,iAdrszCarriageReturn
bl affichageMess
ldr r0,iAdrszMessDecryptText
bl affichageMess
ldr r0,iAdrsBuffer @ text decrypt
ldr r1,iAdrtabPolybius
ldr r2,iAdrsKeyWord
ldr r3,iAdrsBuffer1 @ result buffer
bl decryption
bl affichageMess
ldr r0,iAdrszCarriageReturn
bl affichageMess
100: @ standard end of the program
mov r0, #0 @ return code
mov r7, #EXIT @ request to exit program
svc #0 @ perform the system call
iAdrszCarriageReturn: .int szCarriageReturn
iAdrszMessDecryptText: .int szMessDecryptText
iAdrszMessCryptText: .int szMessCryptText
iAdrszMessStart: .int szMessStart
iAdrsKeyWord: .int sKeyWord
iAdrszText: .int szText
/***************************************************/
/* create 6 * 6 polybius */
/***************************************************/
createPolybius:
push {r1-r4,lr} @ save des registres
ldr r0,iAdrszListChar @ character list address
mov r1,#LGLISTCHAR @ character list size
ldr r2,iAdrtabPolybius
bl shufflestrings @ shuffle list
ldr r0,iAdrszMessPolybius
bl affichageMess
ldr r0,iAdrszTitle @ display polybius lines
bl affichageMess
ldr r0,iAdrszLine1
mov r3,#0
mov r4,#4
1:
ldrb r1,[r2,r3]
strb r1,[r0,r4]
add r4,r4,#2
add r3,r3,#1
cmp r3,#SIZE
blt 1b
bl affichageMess
ldr r0,iAdrszLine2
mov r3,#SIZE
mov r4,#4
2:
ldrb r1,[r2,r3]
strb r1,[r0,r4]
add r4,r4,#2
add r3,r3,#1
cmp r3,#SIZE * 2
blt 2b
bl affichageMess
ldr r0,iAdrszLine3
mov r3,#SIZE * 2
mov r4,#4
3:
ldrb r1,[r2,r3]
strb r1,[r0,r4]
add r4,r4,#2
add r3,r3,#1
cmp r3,#SIZE * 3
blt 3b
bl affichageMess
ldr r0,iAdrszLine4
mov r3,#SIZE * 3
mov r4,#4
4:
ldrb r1,[r2,r3]
strb r1,[r0,r4]
add r4,r4,#2
add r3,r3,#1
cmp r3,#SIZE * 4
blt 4b
bl affichageMess
ldr r0,iAdrszLine5
mov r3,#SIZE * 4
mov r4,#4
5:
ldrb r1,[r2,r3]
strb r1,[r0,r4]
add r4,r4,#2
add r3,r3,#1
cmp r3,#SIZE * 5
blt 5b
bl affichageMess
ldr r0,iAdrszLine6
mov r3,#SIZE * 5
mov r4,#4
6:
ldrb r1,[r2,r3]
strb r1,[r0,r4]
add r4,r4,#2
add r3,r3,#1
cmp r3,#SIZE * 6
blt 6b
bl affichageMess
100:
pop {r1-r4,pc}
iAdrszListChar: .int szListChar
iAdrtabPolybius: .int tabPolybius
iAdrszMessPolybius: .int szMessPolybius
iAdrszTitle: .int szTitle
iAdrszLine1: .int szLine1
iAdrszLine2: .int szLine2
iAdrszLine3: .int szLine3
iAdrszLine4: .int szLine4
iAdrszLine5: .int szLine5
iAdrszLine6: .int szLine6
/***************************************************/
/* generate key word */
/***************************************************/
/* r0 key word address */
generateKey:
push {r1-r12,lr} @ save registers
mov r9,r0
ldr r0,iAdrszFileName @ file name
mov r1,#O_RDWR @ flags
mov r2,#0 @ mode
mov r7,#OPEN @ file open
svc 0
cmp r0,#0 @ error ?
ble 99f
mov r8,r0 @ FD save
ldr r1,iAdrsBuffer @ buffer address
mov r7, #FSTAT @ call systeme NEWFSTAT
svc 0
cmp r0,#0
blt 98f
@ load file size
ldr r1,iAdrsBuffer @ buffer address
ldr r6,[r1,#Stat_size_t] @ file size
lsr r12,r6,#3 @ align size to multiple 4
lsl r12,#3
add r12,#8 @ add for great buffer
sub sp,sp,r12 @ reserve buffer on stack
mov fp,sp @ address save
mov r0,r8
mov r1,fp
mov r2,r12
mov r7,#READ @ call system read file
svc 0
cmp r0,#0 @ error read ?
blt 97f
mov r0,r8
mov r7,#CLOSE @ call system close file
svc 0
cmp r0,#0 @ error close ?
blt 96f
sub sp,sp,#0x1000 @ create array word address on stack
mov r10,sp @ save address array
mov r1,#0
mov r2,fp
mov r5,#0 @ index word ok
mov r3,#0 @ word length
1:
ldrb r4,[fp,r1] @ load character
cmp r4,#0x0D @ end word ?
beq 2f @ yes
add r1,r1,#1
add r3,r3,#1
b 1b
2:
cmp r3,#KEYSIZE @ word length = key length ?
bne 3f @ no ?
mov r0,r2
bl wordControl @ contril if all letters are différent ?
cmp r0,#1
streq r2,[r10,r5,lsl #2] @ if ok store word address in array on stack
addeq r5,r5,#1 @ increment word counter
3:
add r1,r1,#2
cmp r1,r6 @ end ?
beq 4f
add r2,fp,r1 @ new word begin
mov r3,#0 @ init word length
b 1b @ and loop
4:
mov r0,r5 @ number random to total words
bl genereraleas
ldr r2,[r10,r0,lsl #2] @ load address word
mov r1,#0
5: @ copy random word in word result
ldrb r3,[r2,r1]
strb r3,[r9,r1]
add r1,r1,#1
cmp r1,#KEYSIZE
blt 5b
mov r3,#0 @ zero final
strb r3,[r9,r1]
mov r0,r9
b 100f
@ display errors
96:
ldr r0,iAdrszMessClose
bl affichageMess
mov r0,#-1 @ error
b 100f
97:
ldr r0,iAdrszMessRead
bl affichageMess
mov r0,#-1 @ error
b 100f
98:
ldr r0,iAdrszMessStat
bl affichageMess
mov r0,#-1 @ error
b 101f
99:
ldr r0,iAdrszMessOpen
bl affichageMess
mov r0,#-1 @ error
b 101f
100:
add sp,sp,r12
add sp,sp,#0x1000
101:
pop {r1-r12,pc}
iAdrszFileName: .int szFileName
iAdrszMessOpen: .int szMessOpen
iAdrszMessRead: .int szMessRead
iAdrszMessStat: .int szMessStat
iAdrszMessClose: .int szMessClose
iAdrsBuffer: .int sBuffer
/******************************************************************/
/* control if letters are diferents */
/******************************************************************/
/* r0 contains the address of the string */
/* r0 return 1 if Ok else return 0 */
wordControl:
push {r1-r4,lr} @ save registers
mov r1,#0 @ init index 1
1:
ldrb r3,[r0,r1] @ load one character
cmp r3,#0x0D @ end word ?
moveq r0,#1 @ yes is ok
beq 100f @ -> end
add r2,r1,#1 @ init index two
2:
ldrb r4,[r0,r2] @ load one character
cmp r4,#0x0D @ end word ?
addeq r1,r1,#1 @ yes increment index 1
beq 1b @ and loop1
cmp r3,r4 @ caracters equals ?
moveq r0,#0 @ yes is not good
beq 100f @ and end
add r2,r2,#1 @ else increment index 2
b 2b @ and loop 2
100:
pop {r1-r4,pc}
/******************************************************************/
/* key sort by insertion sort */
/******************************************************************/
/* r0 contains the address of String */
/* r1 contains the first element */
/* r2 contains the number of element */
/* r3 contains result address */
keySort:
push {r2-r10,lr} @ save registers
ldr r7,iAdrtabPosit
mov r10,r3
mov r3,#0
0: @ init position array and copy key
strb r3,[r7,r3] @ in result array
ldrb r4,[r0,r3]
strb r4,[r10,r3]
add r3,r3,#1
cmp r3,#KEYSIZE
blt 0b
add r3,r1,#1 @ start index i
1: @ start loop
ldrb r4,[r10,r3] @ load value A[i]
ldrb r8,[r7,r3] @ load position
sub r5,r3,#1 @ index j
2:
ldrb r6,[r10,r5] @ load value A[j]
ldrb r9,[r7,r5] @ load position
cmp r6,r4 @ compare value
ble 3f
add r5,#1 @ increment index j
strb r6,[r10,r5] @ store value A[j+1]
strb r9,[r7,r5] @ store position
subs r5,#2 @ j = j - 1
bge 2b @ loop if j >= 0
3:
add r5,#1 @ increment index j
strb r4,[r10,r5] @ store value A[i] in A[j+1]
strb r8,[r7,r5]
add r3,#1 @ increment index i
cmp r3,r2 @ end ?
blt 1b @ no -> loop
ldr r1,iAdrtabPositInv @ inverse position
mov r2,#0 @ index
4:
ldrb r3,[r7,r2] @ load position index
strb r2,[r1,r3] @ store index in position
add r2,r2,#1 @ increment index
cmp r2,#KEYSIZE @ end ?
blt 4b
mov r0,r10
100:
pop {r2-r10,pc}
iAdrtabPosit: .int tabPosit
iAdrtabPositInv: .int tabPositInv
/******************************************************************/
/* text encryption */
/******************************************************************/
/* r0 contains the address of text */
/* r1 contains polybius address
/* r2 contains the key address */
/* r3 contains result buffer address */
encryption:
push {r2-r10,lr} @ save registers
mov r9,r0 @ save text address
mov r8,r3
mov r10,r1 @ save address polybius
mov r0,r2 @ key address
mov r1,#0 @ first character
mov r2,#KEYSIZE @ key length
ldr r3,iAdrsKeyWordSorted @ result address
bl keySort @ sort leters of key
//bl affichageMess @ if you want display sorted key
//ldr r0,iAdrszCarriageReturn
//bl affichageMess
ldr r3,iAdrsBuffer1
mov r5,#0 @ init text index
mov r4,#0 @ init result index
1:
ldrb r0,[r9,r5] @ load a byte to text
cmp r0,#0 @ end ?
beq 4f
mov r6,#0 @ init index polybius
2:
ldrb r7,[r10,r6] @ load character polybius
cmp r7,r0 @ equal ?
beq 3f
add r6,r6,#1 @ increment index
cmp r6,#SIZEC @ not find -> error
bge 99f
b 2b @ and loop
3:
mov r0,r6
bl convPosCode @ convert position in code character
strb r0,[r3,r4] @ line code character
add r4,r4,#1
strb r1,[r3,r4] @ column code character
add r4,r4,#1
add r5,r5,#1 @ increment text index
b 1b
4:
mov r0,#0 @ zero final -> text result
strb r0,[r3,r4]
mov r5,r3
mov r1,#0 @ index position column
mov r7,#0 @ index text
ldr r2,iAdrtabPositInv
5:
ldrb r0,[r2,r1] @ load position text
7: @ loop to characters transposition
ldrb r6,[r5,r0] @ load character
strb r6,[r8,r7] @ store position final
add r7,r7,#1 @ increment final index
add r0,r0,#KEYSIZE @ add size key
cmp r0,r4 @ end ?
blt 7b
add r1,r1,#1 @ add index column
cmp r1,#KEYSIZE @ < key size
blt 5b @ yes -> loop
mov r6,#0 @ zero final
strb r6,[r8,r7]
mov r0,r8 @ return address encrypted text
b 100f
99: @ display error
ldr r0,iAdrszMessErrorChar
bl affichageMess
mov r0,#-1
100:
pop {r2-r10,pc}
iAdrsBuffer1: .int sBuffer1
iAdrsKeyWordSorted: .int sKeyWordSorted
iAdrszMessErrorChar: .int szMessErrorChar
/******************************************************************/
/* text decryption */
/******************************************************************/
/* r0 contains the address of text */
/* r1 contains polybius address
/* r2 contains the key */
/* r3 contains result buffer */
/* r0 return decoded text */
decryption:
push {r1-r12,lr} @ save registers
mov r4,#0
1: @ compute text length
ldrb r5,[r0,r4]
cmp r5,#0
addne r4,r4,#1
bne 1b
mov r12,r0
mov r11,r1
mov r10,r2
mov r9,r3
mov r0,r4 @ compute line number and remainder
mov r1,#KEYSIZE
bl division
mov r8,r2 @ line number
mov r7,r3 @ remainder characters last line
mov r0,r10 @ key address
mov r1,#0 @ first character
mov r2,#KEYSIZE @ size
ldr r3,iAdrsKeyWordSorted @ result address
bl keySort @ sort key
ldr r10,iAdrtabPositInv @ inverse position
mov r2,#0 @ index colonne tabposit
mov r5,#0 @ text index
mov r0,#0 @ index line store text
mov r1,#0 @ counter line
push {r9} @ save final result address
ldr r9,iAdrsBuffer2
1:
ldrb r3,[r10,r2] @ load position
ldrb r6,[r12,r5] @ load text character
add r3,r3,r0 @ compute position with index line
strb r6,[r9,r3] @ store character in good position
add r5,r5,#1 @ increment index text
cmp r5,r4 @ end ?
bge 4f
add r1,r1,#1 @ increment line
cmp r1,r8 @ line < line size
blt 2f
bgt 11f @ line = line size
sub r3,r3,r0 @ restaure position column
cmp r3,r7 @ position < remainder so add character other line
blt 2f
11:
mov r1,#0 @ init ligne
mov r0,#0 @ init line shift
add r2,r2,#1 @ increment index array position inverse
cmp r2,#KEYSIZE @ end ?
movge r2,#0 @ init index
b 3f
2:
add r0,#KEYSIZE
3:
b 1b
4: @ convertir characters with polybius
mov r3,#0
mov r5,#0
pop {r6} @ restaur final address result
5:
mov r0,r11
ldrb r1,[r9,r3] @ load a first character
add r3,r3,#1
ldrb r2,[r9,r3] @ load a 2ieme character
bl decodPosCode @ decode
strb r0,[r6,r5] @ store result in final result
add r5,r5,#1 @ increment final result index
add r3,r3,#1 @ increment index text
cmp r3,r4 @ end ?
blt 5b
mov r0,#0 @ final zero
strb r0,[r6,r5]
mov r0,r6 @ return final result address
100:
pop {r1-r12,pc}
iAdrsBuffer2: .int sBuffer2
/******************************************************************/
/* convertir position en code */
/******************************************************************/
/* r0 contains the position in polybius */
/* r0 return code1 */
/* r1 return code2 */
convPosCode:
push {r2-r4,lr} @ save registers
ldr r4,iAdrszListCharCode
mov r1,#SIZE
bl division
ldrb r0,[r4,r2]
ldrb r1,[r4,r3]
100:
pop {r2-r4,pc}
iAdrszListCharCode: .int szListCharCode
/******************************************************************/
/* convertir code en character */
/******************************************************************/
/* r0 polybius address */
/* r1 code 1 */
/* r2 code 2 */
/* r0 return character */
decodPosCode:
push {r1-r5,lr} @ save registers
ldr r4,iAdrszListCharCode
mov r3,#0
1:
ldrb r5,[r4,r3]
cmp r5,#0
beq 2f
cmp r5,r1
moveq r1,r3
cmp r5,r2
moveq r2,r3
add r3,r3,#1
b 1b
2:
mov r5,#SIZE
mul r1,r5,r1
add r1,r1,r2
ldrb r0,[r0,r1]
100:
pop {r1-r5,pc}
/******************************************************************/
/* shuffle strings algorithme Fisher-Yates */
/******************************************************************/
/* r0 contains the address of the string */
/* r1 contains string length */
/* r2 contains address result string */
shufflestrings:
push {r1-r4,lr} @ save registers
mov r3,#0
1: @ loop copy string in result
ldrb r4,[r0,r3]
strb r4,[r2,r3]
add r3,r3,#1
cmp r3,r1
ble 1b
sub r1,r1,#1 @ last element
2:
mov r0,r1 @ limit random number
bl genereraleas @ call random
ldrb r4,[r2,r1] @ load byte string index loop
ldrb r3,[r2,r0] @ load byte string random index
strb r3,[r2,r1] @ and exchange
strb r4,[r2,r0]
subs r1,r1,#1
cmp r1,#1
bge 2b
100:
pop {r1-r4,pc} @ restaur registers
/***************************************************/
/* Generation random number */
/***************************************************/
/* r0 contains limit */
genereraleas:
push {r1-r4,lr} @ save registers
ldr r4,iAdriGraine
ldr r2,[r4]
ldr r3,iNbDep1
mul r2,r3,r2
ldr r3,iNbDep1
add r2,r2,r3
str r2,[r4] @ save seed for next call
cmp r0,#0
beq 100f
mov r1,r0 @ divisor
mov r0,r2 @ dividende
bl division
mov r0,r3 @ résult = remainder
100: @ end function
pop {r1-r4,pc} @ restaur registers
iAdriGraine: .int iGraine
iNbDep1: .int 0x343FD
iNbDep2: .int 0x269EC3
/***************************************************/
/* ROUTINES INCLUDE */
/***************************************************/
.include "../affichage.inc"
- Output:
Program 32 bits start. 6 x 6 Polybius square: | A D F G V X --------------- A | C 7 S J 3 Y D | 8 F K A Q U F | 0 E W R 4 I G | B O P Z 9 2 V | 6 M 5 L H 1 X | D X T G V N switchman Encrypted text : DFDAXVFDAGVGGDXXFFXGFDAGDFXA Decrypted text : ATTACKAT1200AM
F#
// ADFGVX cipher. Nigel Galloway: August 23rd., 2021
let polybus=let n=[|yield! {'A'..'Z'}; yield! {'0'..'9'}|] in MathNet.Numerics.Combinatorics.GeneratePermutation 36|>Array.map(fun g->n.[g]),[|'A';'D';'F';'G';'V';'X'|]
let printPolybus(a,g)=printf " "; g|>Array.iter(printf "%c "); printfn ""; printfn " ----------------"
g|>Array.iteri(fun n g->printf " %c|" g; [0..5]|>List.iter(fun g->printf " %c " (Array.item(n*6+g) a)); printfn "")
let c2p n g=let g=(fst>>(Array.findIndex((=) g))) n in let (n:char[])=(snd n) in [|n.[g/n.Length];n.[g%n.Length]|]
let p2c n (g:char[])=Array.item(let n=snd n in (Array.findIndex((=)g.[0]) n)*n.Length+(Array.findIndex((=)g.[1]) n))(fst n)
let fN(g:string)=let e,d=let n=g|>Seq.sort|>List.ofSeq in (g|>Seq.mapi(fun i l->(List.findIndex((=)l)n)-i)|>Array.ofSeq,n|>Seq.mapi(fun i l->(Seq.findIndex((=)l)g)-i)|>Array.ofSeq)
(fun i->i+(e.[i%g.Length])),(fun i->i+(d.[i%g.Length]))
let ADFGVX n (g:string)=let pE,pD=fN g
(fun(s:string)->let a,b=s|>Seq.collect(c2p n)|>Array.ofSeq|>Array.splitAt(2*s.Length-(2*s.Length)%g.Length)
Array.append(Array.permute(pE) a)(Array.permute(fst(fN(g.[..b.Length-1]))) b)|>System.String),
(fun(s:string)->let a,b=s.ToCharArray()|>Array.splitAt(s.Length-(s.Length)%g.Length)
Array.append(Array.permute(pD) a)(Array.permute(snd(fN(g.[..b.Length-1]))) b)|>Array.chunkBySize 2|>Array.map(p2c n)|>System.String)
printPolybus polybus
let encrypt,decrypt=ADFGVX polybus "nigel" //Using "nigel" as the key no hacker will guess that!
let n=encrypt "ATTACKAT1200AM" in printfn $"\nATTACKAT1200AM encrypted is %s{n} which decrypted is %s{decrypt n}"
- Output:
A D F G V X ---------------- A| 6 U 9 B 2 H D| C G O N Y R F| T 8 D E V K G| 1 Q X P L W V| Z 0 I A J S X| 7 4 3 M F 5 ATTACKAT1200AM encrypted is AFGFVDGVAAGVXFFAAGVADVDVVGXG which decrypted is ATTACKAT1200AM
Go
package main
import (
"bytes"
"fmt"
"io/ioutil"
"log"
"math/rand"
"sort"
"strings"
"time"
)
var adfgvx = "ADFGVX"
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func distinct(bs []byte) []byte {
var u []byte
for _, b := range bs {
if !bytes.Contains(u, []byte{b}) {
u = append(u, b)
}
}
return u
}
func allAsciiAlphaNum(word []byte) bool {
for _, b := range word {
if !((b >= 48 && b <= 57) || (b >= 65 && b <= 90) || (b >= 97 && b <= 122)) {
return false
}
}
return true
}
func orderKey(key string) []int {
temp := make([][2]byte, len(key))
for i := 0; i < len(key); i++ {
temp[i] = [2]byte{key[i], byte(i)}
}
sort.Slice(temp, func(i, j int) bool { return temp[i][0] < temp[j][0] })
res := make([]int, len(key))
for i := 0; i < len(key); i++ {
res[i] = int(temp[i][1])
}
return res
}
func createPolybius() []string {
temp := []byte(alphabet)
rand.Shuffle(36, func(i, j int) {
temp[i], temp[j] = temp[j], temp[i]
})
alphabet = string(temp)
fmt.Println("6 x 6 Polybius square:\n")
fmt.Println(" | A D F G V X")
fmt.Println("---------------")
p := make([]string, 6)
for i := 0; i < 6; i++ {
fmt.Printf("%c | ", adfgvx[i])
p[i] = alphabet[6*i : 6*(i+1)]
for _, c := range p[i] {
fmt.Printf("%c ", c)
}
fmt.Println()
}
return p
}
func createKey(n int) string {
if n < 7 || n > 12 {
log.Fatal("Key should be within 7 and 12 letters long.")
}
bs, err := ioutil.ReadFile("unixdict.txt")
if err != nil {
log.Fatal("Error reading file")
}
words := bytes.Split(bs, []byte{'\n'})
var candidates [][]byte
for _, word := range words {
if len(word) == n && len(distinct(word)) == n && allAsciiAlphaNum(word) {
candidates = append(candidates, word)
}
}
k := string(bytes.ToUpper(candidates[rand.Intn(len(candidates))]))
fmt.Println("\nThe key is", k)
return k
}
func encrypt(polybius []string, key, plainText string) string {
temp := ""
outer:
for _, ch := range []byte(plainText) {
for r := 0; r <= 5; r++ {
for c := 0; c <= 5; c++ {
if polybius[r][c] == ch {
temp += fmt.Sprintf("%c%c", adfgvx[r], adfgvx[c])
continue outer
}
}
}
}
colLen := len(temp) / len(key)
// all columns need to be the same length
if len(temp)%len(key) > 0 {
colLen++
}
table := make([][]string, colLen)
for i := 0; i < colLen; i++ {
table[i] = make([]string, len(key))
}
for i := 0; i < len(temp); i++ {
table[i/len(key)][i%len(key)] = string(temp[i])
}
order := orderKey(key)
cols := make([][]string, len(key))
for i := 0; i < len(key); i++ {
cols[i] = make([]string, colLen)
for j := 0; j < colLen; j++ {
cols[i][j] = table[j][order[i]]
}
}
res := make([]string, len(cols))
for i := 0; i < len(cols); i++ {
res[i] = strings.Join(cols[i], "")
}
return strings.Join(res, " ")
}
func decrypt(polybius []string, key, cipherText string) string {
colStrs := strings.Split(cipherText, " ")
// ensure all columns are same length
maxColLen := 0
for _, s := range colStrs {
if len(s) > maxColLen {
maxColLen = len(s)
}
}
cols := make([][]string, len(colStrs))
for i, s := range colStrs {
var ls []string
for _, c := range s {
ls = append(ls, string(c))
}
if len(s) < maxColLen {
cols[i] = make([]string, maxColLen)
copy(cols[i], ls)
} else {
cols[i] = ls
}
}
table := make([][]string, maxColLen)
order := orderKey(key)
for i := 0; i < maxColLen; i++ {
table[i] = make([]string, len(key))
for j := 0; j < len(key); j++ {
table[i][order[j]] = cols[j][i]
}
}
temp := ""
for i := 0; i < len(table); i++ {
temp += strings.Join(table[i], "")
}
plainText := ""
for i := 0; i < len(temp); i += 2 {
r := strings.IndexByte(adfgvx, temp[i])
c := strings.IndexByte(adfgvx, temp[i+1])
plainText = plainText + string(polybius[r][c])
}
return plainText
}
func main() {
rand.Seed(time.Now().UnixNano())
plainText := "ATTACKAT1200AM"
polybius := createPolybius()
key := createKey(9)
fmt.Println("\nPlaintext :", plainText)
cipherText := encrypt(polybius, key, plainText)
fmt.Println("\nEncrypted :", cipherText)
plainText2 := decrypt(polybius, key, cipherText)
fmt.Println("\nDecrypted :", plainText2)
}
- Output:
Sample run:
6 x 6 Polybius square: | A D F G V X --------------- A | R W H N I 7 D | 1 J O 2 P 5 F | 3 A T 4 M 9 G | D U L K V 0 V | Z B E F 6 Q X | G Y S 8 C X The key is EUCHARIST Plaintext : ATTACKAT1200AM Encrypted : FDG FGG FVDV FFX FFF FFX DDD XAF DGG Decrypted : ATTACKAT1200AM
J
Implementation:
polybius=: {{6 6$8 u:({~?~&#)(48+i.10),65+i.26}}
lenword=: {{ ;({~ ?@#)(#~ (-:~.)@>)(#~ y=#@>)cutLF fread'unixdict.txt'}}
ADFGVX=: {{ deb,' ',.n/:~|:(-#n)]\'ADFGVX'{~,($m)#:(,m)i.y([-.-.),m }}
XVGFDA=: {{ (,m){~($m)#.'ADFGVX'i._2]\deb,|:(>cut y)/:/:n }}
Example:
echo W=: lenword 9
roughcast
echo P=: polybius ''
PV5M6Q
KR0391
4ZS7LA
FUT28E
GXOBYW
ICJDNH
echo E=: P ADFGVX W 'ATTACKAT1200AM'
FFF FGF FFF GXD XDG FDGG XDX XXA GAD
echo D=: P XVGFDA W E
ATTACKAT1200AM
That said, note that we could also eliminate spaces from the encrypted text, as they are recoverable if we have the key word.
spaces=: {{deb y#inv~,0,.(/:n){*|:_9]\>:i.#y=.y-.' '}}
echo S=: E-.' '
FFFFGFFFFGXDXDGFDGGXDXXXAGAD
P spaces W S
FFF FGF FFF GXD XDG FDGG XDX XXA GAD
(Technically, we do not need the Polybius square to recover the spaces, but it's passed as an argument here for symmetry.)
Julia
"""
The ADFGVX cipher.
See also eg. https://www.nku.edu/~christensen/092hnr304%20ADFGVX.pdf
"""
using Random
""" The WWI German ADFGVX cipher. """
struct ADFGVX
polybius::Vector{Char}
pdim::Int
key::Vector{Char}
keylen::Int
alphabet::Vector{Char}
encode::Dict{Char, Vector{Char}}
decode::Dict{Vector{Char}, Char}
end
""" ADFGVX constructor, takes 2 strings, option for third string if polybius len != 36 """
function ADFGVX(s, k, alph = "ADFGVX")
poly = collect(uppercase(s))
pdim = isqrt(length(poly))
al = collect(uppercase(alph))
enco::Dict = Dict([(poly[(i - 1) * pdim + j] => [al[i], al[j]])
for i in 1:pdim, j in 1:pdim])
deco = Dict(last(p) => first(p) for p in enco)
@assert pdim^2 == length(poly) && pdim == length(al)
return ADFGVX(poly, pdim, collect(uppercase(k)), length(k), al, enco, deco)
end
""" Encrypt with the ADFGVX cipher. """
function encrypt(s::String, k::ADFGVX)
chars = reduce(vcat, [k.encode[c] for c in
filter(c -> c in k.polybius, collect(uppercase(s)))])
colvecs = [lett => chars[i:k.keylen:length(chars)] for (i, lett) in enumerate(k.key)]
sort!(colvecs, lt = (x, y) -> first(x) < first(y))
return String(mapreduce(p -> last(p), vcat, colvecs))
end
""" Decrypt with the ADFGVX cipher. Does not depend on spacing of encoded text """
function decrypt(s::String, k::ADFGVX)
chars = filter(c -> c in k.alphabet, collect(uppercase(s)))
sortedkey = sort(collect(k.key))
order = [findfirst(c -> c == ch, k.key) for ch in sortedkey]
originalorder = [findfirst(c -> c == ch, sortedkey) for ch in k.key]
a, b = divrem(length(chars), k.keylen)
strides = [a + (b >= i ? 1 : 0) for i in order] # shuffled column lengths
starts = accumulate(+, strides[begin:end-1], init=1) # shuffled starts of columns
pushfirst!(starts, 1) # starting index
ends = [starts[i] + strides[i] - 1 for i in 1:k.keylen] # shuffled ends of columns
cols = [chars[starts[i]:ends[i]] for i in originalorder] # get reordered columns
pairs, nrows = Char[], (length(chars) - 1) ÷ k.keylen + 1 # recover the rows
for i in 1:nrows, j in 1:k.keylen
(i - 1) * k.keylen + j > length(chars) && break
push!(pairs, cols[j][i])
end
return String([k.decode[[pairs[i], pairs[i + 1]]] for i in 1:2:length(pairs)-1])
end
const POLYBIUS = String(shuffle(collect("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")))
const KEY = read("unixdict.txt", String) |>
v -> split(v, r"\s+") |>
v -> filter(w -> (n = length(w); n == 9 && n == length(unique(collect(w)))), v) |>
shuffle |> first |> uppercase
const SECRETS, message = ADFGVX(POLYBIUS, KEY), "ATTACKAT1200AM"
println("Polybius: $POLYBIUS, Key: $KEY")
println("Message: $message")
encoded = encrypt(message, SECRETS)
decoded = decrypt(encoded, SECRETS)
println("Encoded: $encoded")
println("Decoded: $decoded")
- Output:
Polybius: L4VZJIB8OXGFM1H3CTNKU9PE75WQ2DAYRS06, Key: SUNFLOWER Message: ATTACKAT1200AM Encoded: AFAXXVFAXFDXXFVFDFXVVAAGVXXX Decoded: ATTACKAT1200AM
Nim
It started as a translation, but actually we use a different method, better suited to Nim, to encrypt and decrypt. And there are many other differences. Output is similar though.
import algorithm, random, sequtils, strutils, sugar, tables
const Adfgvx = "ADFGVX"
type PolybiusSquare = array[6, array[6, char]]
iterator items(p: PolybiusSquare): (int, int, char) =
## Yield Polybius square characters preceded by row and column numbers.
for r in 0..5:
for c in 0..5:
yield (r, c, p[r][c])
proc initPolybiusSquare(): PolybiusSquare =
## Initialize a 6x6 Polybius square.
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
alphabet.shuffle()
for r in 0..5:
for c in 0..5:
result[r][c] = alphabet[6 * r + c]
proc createKey(n: Positive): string =
## Create a key using a word from "unixdict.txt".
doAssert n in 7..12, "Key should be within 7 and 12 letters long."
let candidates = collect(newSeq):
for word in "unixdict.txt".lines:
if word.len == n and
word.deduplicate().len == n and
word.allCharsInSet(Letters + Digits): word
result = candidates[rand(candidates.high)].toUpperAscii
func encrypt(plainText: string; polybius: PolybiusSquare; key: string): string =
## Encrypt "plaintext" using the given Polybius square and the given key.
# Replace characters by row+column letters.
var str: string
for ch in plainText:
for (r, c, val) in polybius:
if val == ch:
str.add Adfgvx[r] & Adfgvx[c]
# Build ordered table of columns and sort it by key value.
var cols: OrderedTable[char, string]
for i, ch in str:
let tkey = key[i mod key.len]
cols.mgetOrPut(tkey, "").add ch
cols.sort(cmp)
# Build cipher text from sorted column table values.
for s in cols.values:
result.addSep(" ")
result.add s
func decrypt(cipherText: string; polybius: PolybiusSquare; key: string): string =
## Decrypt "cipherText" using the given Polybius square and the given key.
# Build list of columns.
let skey = sorted(key)
var cols = newSeq[string](key.len)
var idx = 0
for col in cipherText.split(' '):
cols[key.find(skey[idx])] = col
inc idx
# Build string of row+column values.
var str: string
for i in 0..key.high:
for col in cols:
if i < col.len: str.add col[i]
# Build plain text from row+column values.
for i in countup(0, str.len - 2, 2):
let r = Adfgvx.find(str[i])
let c = Adfgvx.find(str[i+1])
result.add polybius[r][c]
randomize()
var polybius = initPolybiusSquare()
echo "6 x 6 Polybius square:\n"
echo " | A D F G V X"
echo "---------------"
for i, row in polybius:
echo Adfgvx[i], " | ", row.join(" ")
let key = createKey(9)
echo "\nThe key is ", key
const PlainText = "ATTACKAT1200AM"
echo "\nPlaintext : ", PlainText
let cipherText = PlainText.encrypt(polybius, key)
echo "\nEncrypted : ", cipherText
let plainText = cipherText.decrypt(polybius, key)
echo "\nDecrypted : ", plainText
- Output:
6 x 6 Polybius square: | A D F G V X --------------- A | U 1 C N H F D | E M 4 R S G F | P I 8 9 6 5 G | X 2 Z B 7 K V | A 3 Y V O D X | 0 W Q T J L The key is PHAGOCYTE Plaintext : ATTACKAT1200AM Encrypted : XXX GXA ADD GVA AGD XAX VFGD AAA VGV Decrypted : ATTACKAT1200AM
Perl
#!/usr/bin/perl
use strict; # https://rosettacode.org/wiki/ADFGVX_cipher
use warnings;
use List::Util qw( shuffle );
my $plaintext = 'ATTACKAT1200AM';
my $keysize = 9;
my $polybius = <<END;
| A D F G V X
--+------------
A | x x x x x x
D | x x x x x x
F | x x x x x x
G | x x x x x x
V | x x x x x x
X | x x x x x x
END
$polybius =~ s/x/$_/ for my @letters = shuffle "A" .. 'Z' , 0 .. 9;
print "Polybius square =\n\n$polybius\n";
my %char2pair;
@char2pair{ @letters } = glob '{A,D,F,G,V,X}' x 2; # map chars to pairs
my %pair2char = reverse %char2pair; # map pairs to chars
my ($keyword) = shuffle grep !/(.).*\1/,
do { local (@ARGV, $/) = 'unixdict.txt'; <> =~ /^.{$keysize}$/gm };
my ($n, @deorder) = 0;
my @reorder = map /.(.+)/, sort map $_ . $n++, split //, $keyword;
@deorder[@reorder] = 0 .. $#reorder;
print " keyword = $keyword\n\nplaintext = $plaintext\n\n";
my $encoded = encode( $plaintext, \%char2pair, \@reorder );
print " encoded = $encoded\n\n";
my $decoded = decode( $encoded, \%pair2char, \@deorder );
print " decoded = $decoded\n";
sub encode
{
my ($plain, $c2p, $order) = @_;
my $len = @$order;
join ' ', (transpose( $plain =~ s/./$c2p->{$&}/gr =~ /.{1,$len}/g ))[@$order];
}
sub decode
{
my ($encoded, $p2c, $order) = @_;
(join '', transpose((split ' ', $encoded)[@$order])) =~ s/../$p2c->{$&}/gr;
}
sub transpose { map join('', map {s/.// ? $& : ''} @_), 1 .. length $_[0] }
- Output:
Polybius square = | A D F G V X --+------------ A | 6 V Y E P N D | 1 C M 9 L H F | 8 T O U D F G | A R G 7 S 5 V | J 2 W Q I Z X | K B 3 0 X 4 keyword = benchmark plaintext = ATTACKAT1200AM encoded = GDG GDVF DGG AXD FAX DAD DFG FAX ADA decoded = ATTACKAT1200AM
Phix
We can make some nice use of the standard builtin routines here, with only a modest amount of whitespace cleanup.
with javascript_semantics constant ADFGVX = "ADFGVX", ALEPH = tagset('Z','A')&tagset('9','0') function create_polybius() string aleph = shuffle(ALEPH) -- string aleph = "U1CNHFEM4RSGPI8965X2ZB7KA3YVOD0WQTJL" -- Nim -- string aleph = "T71VB5HYG2JKIQM8REOPDUNCZ063FXAW9S4L" -- Wren -- string aleph = "NA1C3H8TB2OME5WRPD4F6G7I9J0KLQSUVXYZ" -- wp sequence tmp = split(join_by(aleph,1,6," "),'\n') printf(1,"6 x 6 Polybius square:\n") printf(1," | A D F G V X\n") printf(1,"---------------\n") for i=1 to length(tmp) do printf(1,"%s | %s\n",{ADFGVX[i],tmp[i]}) end for return aleph end function function lnua(string word, integer n) return length(word)==n and length(unique(word))==n and length(filter(word,"in",ALEPH))==n end function function create_key(integer n) assert(n>=7 and n<=12) sequence candidates = filter(upper(unix_dict()),lnua,n) string res = candidates[rand(length(candidates))] -- string res = "PHAGOCYTE" -- Nim -- string res = "SUNFLOWER" -- Wren -- string res = "PRIVACY" -- wp printf(1,"\nThe key is %s\n",{res}) return res end function function encrypt(string polybius, key, plaintext) integer l = length(key) sequence tags = custom_sort(key,tagset(l)), res = "" for i=1 to length(plaintext) do integer k = find(plaintext[i],polybius) if k then -- (simply ignore any non-alphanum) res &= ADFGVX[floor((k-1)/6)+1]& ADFGVX[remainder((k-1),6)+1] end if end for res = substitute(join(columnize(split_by(res,l),tags,' '))," "," ") return res end function function decrypt(string polybius, key, encrypted) integer l = length(key) sequence tags = custom_sort(key,tagset(l)), tmp = columnize(split(encrypted,' '),{},' ') tmp = trim(join(apply(true,extract,{tmp,{tags},true}),"")) string plaintext = "" for i=1 to length(tmp) by 2 do integer r = find(tmp[i],ADFGVX)-1, c = find(tmp[i+1],ADFGVX) plaintext &= polybius[r*6+c] end for return plaintext end function string polybius = create_polybius(), key = create_key(9), plaintext = "ATTACKAT1200AM", encrypted = encrypt(polybius,key,plaintext), decrypted = decrypt(polybius,key,encrypted) printf(1,"\nPlainText : %s\n\nEncrypted : %s\n\nDecrypted : %s\n", {plaintext, encrypted, decrypted})
Output matches Wren/Nim/wp when the appropriate lines are uncommented.
Python
This version and the Julia version do not reveal the key length by preserving spaces between columns, which completely removes column information, not just hiding which columns are first in the encoding. According to historical sources the original encrypted text was spaced in 5 character blocks regardless of message and key length, which means that decryption should not rely on spacing.
"""
The ADFGVX cipher implemented as a Python class
See also eg. https://www.nku.edu/~christensen/092hnr304%20ADFGVX.pdf
"""
from random import shuffle, choice
from itertools import product, accumulate
from numpy import floor, sqrt
class ADFGVX:
""" The WWI German ADFGVX cipher. """
def __init__(self, spoly, k, alph='ADFGVX'):
self.polybius = list(spoly.upper())
self.pdim = int(floor(sqrt(len(self.polybius))))
self.key = list(k.upper())
self.keylen = len(self.key)
self.alphabet = list(alph)
pairs = [p[0] + p[1] for p in product(self.alphabet, self.alphabet)]
self.encode = dict(zip(self.polybius, pairs))
self.decode = dict((v, k) for (k, v) in self.encode.items())
def encrypt(self, msg):
""" Encrypt with the ADFGVX cipher. """
chars = list(''.join([self.encode[c] for c in msg.upper() if c in self.polybius]))
colvecs = [(lett, chars[i:len(chars):self.keylen]) \
for (i, lett) in enumerate(self.key)]
colvecs.sort(key=lambda x: x[0])
return ''.join([''.join(a[1]) for a in colvecs])
def decrypt(self, cod):
""" Decrypt with the ADFGVX cipher. Does not depend on spacing of encoded text """
chars = [c for c in cod if c in self.alphabet]
sortedkey = sorted(self.key)
order = [self.key.index(ch) for ch in sortedkey]
originalorder = [sortedkey.index(ch) for ch in self.key]
base, extra = divmod(len(chars), self.keylen)
strides = [base + (1 if extra > i else 0) for i in order] # shuffled column lengths
starts = list(accumulate(strides[:-1], lambda x, y: x + y)) # shuffled starts of columns
starts = [0] + starts # starting index
ends = [starts[i] + strides[i] for i in range(self.keylen)] # shuffled ends of columns
cols = [chars[starts[i]:ends[i]] for i in originalorder] # get reordered columns
pairs = [] # recover the rows
for i in range((len(chars) - 1) // self.keylen + 1):
for j in range(self.keylen):
if i * self.keylen + j < len(chars):
pairs.append(cols[j][i])
return ''.join([self.decode[pairs[i] + pairs[i + 1]] for i in range(0, len(pairs), 2)])
if __name__ == '__main__':
PCHARS = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
shuffle(PCHARS)
POLYBIUS = ''.join(PCHARS)
with open('unixdict.txt') as fh:
WORDS = [w for w in (fh.read()).split() \
if len(w) == 9 and len(w) == len(set(list(w)))]
KEY = choice(WORDS)
SECRET, MESSAGE = ADFGVX(POLYBIUS, KEY), 'ATTACKAT1200AM'
print(f'Polybius: {POLYBIUS}, key: {KEY}')
print('Message: ', MESSAGE)
ENCODED = SECRET.encrypt(MESSAGE)
DECODED = SECRET.decrypt(ENCODED)
print('Encoded: ', ENCODED)
print('Decoded: ', DECODED)
- Output:
Polybius: A9GKMF1DQRSBVX8Z0WTEJLOPY5U4CN2H76I3, key: volcanism Message: ATTACKAT1200AM Encoded: GAFAAVAAAGGFVAAAGVAAAADAAVXV Decoded: ATTACKAT1200AM
Raku
Slightly different results from every other entry so far. See discussion page for reasons. It is impossible to tell from casual observation which column comes first in the Raku example. In every other (so far), the sub group with 4 characters is the first column.
srand 123456; # For repeatability
my @header = < A D F G V X >;
my $polybius = (flat 'A'..'Z', 0..9).pick(*).join;
my $key-length = 9;
my $key = uc 'unixdict.txt'.IO.words.grep( { (.chars == $key-length) && (+.comb.Set == +.comb) } ).roll;
my %cypher = (@header X~ @header) Z=> $polybius.comb;
my $message = 'Attack at 1200AM';
use Terminal::Boxer;
say "Key: $key\n";
say "Polybius square:\n", ss-box :7col, :3cw, :indent("\t"), '', |@header, |(@header Z $polybius.comb.batch: 6);
say "Message to encode: $message";
say "\nEncoded: " ~ my $encoded = encode $message;
say "\nDecoded: " ~ decode $encoded;
sub encode ($text is copy) {
$text = $text.uc.comb(/<[A..Z 0..9]>/).join;
my @order = $key.comb.pairs.sort( *.value )».key;
my @encode = %cypher.invert.hash{ $text.comb }.join.comb.batch($key-length).map: { [$_] };
((^$key-length).map: { @encode».[@order]».grep( *.defined )».[$_].grep( *.defined ).join }).Str;
}
sub decode ($text is copy) {
my @text = $text.split(' ')».comb;
my $chars = @text[0].chars;
$_ = flat |$_, ' ' if .chars < $chars for @text;
my @order = $key.comb.pairs.sort( *.value )».key.pairs.sort( *.value )».key;
%cypher{ ( grep { /\w/ }, flat [Z] @order.map( { |@text.batch($key-length)».[$_] } ) ).batch(2)».join }.join;
}
- Output:
Key: GHOSTLIKE Polybius square: ┌───┬───┬───┬───┬───┬───┬───┐ │ │ A │ D │ F │ G │ V │ X │ ├───┼───┼───┼───┼───┼───┼───┤ │ A │ H │ O │ S │ 5 │ 7 │ Q │ ├───┼───┼───┼───┼───┼───┼───┤ │ D │ 0 │ I │ 6 │ J │ C │ V │ ├───┼───┼───┼───┼───┼───┼───┤ │ F │ 4 │ 8 │ R │ X │ G │ A │ ├───┼───┼───┼───┼───┼───┼───┤ │ G │ Y │ F │ P │ B │ Z │ 3 │ ├───┼───┼───┼───┼───┼───┼───┤ │ V │ M │ W │ 9 │ D │ 1 │ N │ ├───┼───┼───┼───┼───┼───┼───┤ │ X │ 2 │ E │ U │ T │ L │ K │ └───┴───┴───┴───┴───┴───┴───┘ Message to encode: Attack at 1200AM Encoded: DVVA FVX XXA FGF XVX GXA XXD GFA XXD
Rust
// This version formats the encrypted text in 5 character blocks, as the historical version apparently did.
use fastrand::shuffle;
use std::collections::HashMap;
static ADFGVX: &str = "ADFGVX";
#[derive(Clone, Eq, Hash, PartialEq)]
struct CPair(char, char);
/// The WWI German ADFGVX cipher.
struct AdfgvxCipher {
polybius: Vec<char>,
key: Vec<char>,
encode: HashMap<char, CPair>,
decode: HashMap<CPair, char>,
}
/// Set up the encoding and decoding for the ADFGVX cipher.
fn cipher(allowed_chars: String, encrypt_key: String) -> AdfgvxCipher {
let alphabet = allowed_chars.to_uppercase().chars().collect::<Vec<_>>();
assert!(alphabet.len() == ADFGVX.len() * ADFGVX.len());
let mut polybius = alphabet.clone();
shuffle(&mut polybius);
let key = encrypt_key.to_uppercase().chars().collect::<Vec<_>>();
let adfgvx: Vec<char> = String::from(ADFGVX).chars().collect();
let mut pairs: Vec<CPair> = [CPair(' ', ' '); 0].to_vec();
for c1 in &adfgvx {
for c2 in &adfgvx {
pairs.push(CPair(*c1, *c2));
}
}
let mut encode: HashMap<char, CPair> = HashMap::new();
for i in 0..pairs.len() {
encode.insert(polybius[i], pairs[i].clone());
}
let mut decode = HashMap::new();
for (k, v) in &encode {
decode.insert(v.clone(), *k);
}
return AdfgvxCipher {
polybius,
key,
encode,
decode,
};
}
/// Encrypt with the ADFGVX cipher.
fn encrypt(a: &AdfgvxCipher, msg: String) -> String {
let umsg: Vec<char> = msg
.clone()
.to_uppercase()
.chars()
.filter(|c| a.polybius.contains(c))
.collect();
let mut fractionated = vec![' '; 0].to_vec();
for c in umsg {
let cp = a.encode.get(&c).unwrap();
fractionated.push(cp.0);
fractionated.push(cp.1);
}
let ncols = a.key.len();
let extra = fractionated.len() % ncols;
if extra > 0 {
fractionated.append(&mut vec!['\u{00}'; ncols - extra]);
}
let nrows = fractionated.len() / ncols;
let mut sortedkey = a.key.clone();
sortedkey.sort();
let mut ciphertext = String::from("");
let mut textlen = 0;
for j in 0..ncols {
let k = a.key.iter().position(|c| *c == sortedkey[j]).unwrap();
for i in 0..nrows {
let ch: char = fractionated[i * ncols + k];
if ch != '\u{00}' {
ciphertext.push(ch);
textlen += 1;
if textlen % 5 == 0 {
ciphertext.push(' ');
}
}
}
}
return ciphertext;
}
/// Decrypt with the ADFGVX cipher. Does not depend on spacing of encoded text
fn decrypt(a: &AdfgvxCipher, cod: String) -> String {
let chars: Vec<char> = cod.chars().filter(|c| *c != ' ').collect();
let mut sortedkey = a.key.clone();
sortedkey.sort();
let order: Vec<usize> = sortedkey
.iter()
.map(|c| a.key.iter().position(|kc| kc == c).unwrap())
.collect();
let originalorder: Vec<usize> = a
.key
.iter()
.map(|c| sortedkey.iter().position(|kc| kc == c).unwrap())
.collect();
let q = chars.len() / a.key.len();
let r = chars.len() % a.key.len();
let strides: Vec<usize> = order
.iter()
.map(|i| {q + {if r > *i {1} else {0}}}).collect();
let mut starts: Vec<usize> = vec![0_usize; 1].to_vec();
let mut stridesum = 0;
for i in 0..strides.len() - 1 {
stridesum += strides[i];
starts.push(stridesum);
}
let ends: Vec<usize> = (0..a.key.len()).map(|i| (starts[i] + strides[i])).collect(); // shuffled ends of columns
let cols: Vec<Vec<char>> = originalorder
.iter()
.map(|i| (chars[starts[*i]..ends[*i]]).to_vec())
.collect(); // get reordered columns
let nrows = (chars.len() - 1) / a.key.len() + 1;
let mut fractionated = vec![' '; 0].to_vec();
for i in 0..nrows {
for j in 0..a.key.len() {
if i < cols[j].len() {
fractionated.push(cols[j][i]);
}
}
}
let mut decoded = String::from("");
for i in 0..fractionated.len() - 1 {
if i % 2 == 0 {
let cp = CPair(fractionated[i], fractionated[i + 1]);
decoded.push(*a.decode.get(&cp).unwrap());
}
}
return decoded;
}
fn main() {
let msg = String::from("ATTACKAT1200AM");
let encrypt_key = String::from("volcanism");
let allowed_chars: String = String::from("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789");
let adf = cipher(allowed_chars, encrypt_key.clone());
println!("Message: {msg}");
println!("Polybius: {:?}", adf.polybius.iter().collect::<String>());
println!("Key: {encrypt_key}");
let encrypted_message = encrypt(&adf, msg.clone());
println!("Encoded: {encrypted_message}");
let decoded = decrypt(&adf, encrypted_message);
println!("Decoded: {decoded:?}");
}
- Output:
Message: ATTACKAT1200AM Polybius: "EKFG7HOYZQPUD536SI2NT0W4A1XJLM8CBRV9" Key: volcanism Encoded: GAGFV GVFVG DGXDV FGGAA AAVAV DGX Decoded: "ATTACKAT1200AM"
Decoded: ATTACKAT1200AM
Wren
import "random" for Random
import "/ioutil" for FileUtil
import "/seq" for Lst
import "/str" for Char, Str
var rand = Random.new()
var adfgvx = "ADFGVX"
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toList
var createPolybius = Fn.new {
rand.shuffle(alphabet)
var p = Lst.chunks(alphabet, 6)
System.print("6 x 6 Polybius square:\n")
System.print(" | A D F G V X")
System.print("---------------")
for (i in 0...p.count) {
System.write("%(adfgvx[i]) | ")
System.print(p[i].join(" "))
}
return p
}
var createKey = Fn.new { |n|
if (n < 7 || n > 12) Fiber.abort("Key should be within 7 and 12 letters long.")
var candidates = FileUtil.readLines("unixdict.txt").where { |word|
return word.count == n && Lst.distinct(word.toList).count == n &&
word.all { |ch| Char.isAsciiAlphaNum(ch) }
}.toList
var k = Str.upper(candidates[rand.int(candidates.count)])
System.print("\nThe key is %(k)")
return k
}
// helper function to sort the key into alphabetical order
// and return a list of the original indices of its letters.
var orderKey = Fn.new { |key|
var temp = (0...key.count).map { |i| [key[i], i] }.toList
temp.sort { |x, y| x[0].bytes[0] < y[0].bytes[0] }
return temp.map { |e| e[1] }.toList
}
var encrypt = Fn.new { |polybius, key, plainText|
var temp = ""
for (ch in plainText) {
var outer = false
for (r in 0..5) {
for (c in 0..5) {
if (polybius[r][c] == ch) {
temp = temp + adfgvx[r] + adfgvx[c]
outer = true
break
}
}
if (outer) break
}
}
var colLen = (temp.count / key.count).floor
// all columns need to be the same length
if (temp.count % key.count > 0) colLen = colLen + 1
var table = Lst.chunks(temp.toList, key.count)
var lastLen = table[-1].count
if (lastLen < key.count) table[-1] = table[-1] + ([""] * (key.count - lastLen))
var order = orderKey.call(key)
var cols = List.filled(key.count, null)
for (i in 0...cols.count) {
cols[i] = List.filled(colLen, null)
for (j in 0...table.count) cols[i][j] = table[j][order[i]]
}
return cols.map { |col| col.join() }.join(" ")
}
var decrypt = Fn.new { |polybius, key, cipherText|
var colStrs = cipherText.split(" ")
// ensure all columns are same length
var maxColLen = colStrs.reduce(0) { |max, col| max = (col.count > max) ? col.count : max }
var cols = colStrs.map { |s|
return (s.count < maxColLen) ? s.toList + ([""] * (maxColLen - s.count)) : s.toList
}.toList
var table = List.filled(maxColLen, null)
var order = orderKey.call(key)
for (i in 0...maxColLen) {
table[i] = List.filled(key.count, "")
for (j in 0...key.count) table[i][order[j]] = cols[j][i]
}
var temp = table.map { |row| row.join("") }.join("")
var plainText = ""
var i = 0
while (i < temp.count) {
var r = adfgvx.indexOf(temp[i])
var c = adfgvx.indexOf(temp[i+1])
plainText = plainText + polybius[r][c]
i = i + 2
}
return plainText
}
var plainText = "ATTACKAT1200AM"
var polybius = createPolybius.call()
var key = createKey.call(9)
System.print("\nPlaintext : %(plainText)")
var cipherText = encrypt.call(polybius, key, plainText)
System.print("\nEncrypted : %(cipherText)")
var plainText2 = decrypt.call(polybius, key, cipherText)
System.print("\nDecrypted : %(plainText2)")
- Output:
Sample run:
6 x 6 Polybius square: | A D F G V X --------------- A | T 7 1 V B 5 D | H Y G 2 J K F | I Q M 8 R E G | O P D U N C V | Z 0 6 3 F X X | A W 9 S 4 L The key is SUNFLOWER Plaintext : ATTACKAT1200AM Encrypted : AAA AXD AAV AXV AAD GFF XXDF ADG XAX Decrypted : ATTACKAT1200AM
REXX
/* REXX */
cls
eol=x2c(0D0A) ; msg="ATTACKAT1200AM"
keyword= upper('lifeguard') ; cyph= 'ADFGVX'
s_sort= keyword ; new_key= ''
do while length(s_sort) > 0
nmax= 0
do i=1 to length(s_sort)
ch= substr(s_sort,i,1)
num= c2d(ch)
if num > nmax then do
nmax= num
max_i = i
end
end
s_sort= delstr(s_sort,max_i,1)
new_key= d2c(nmax)||new_key
end /* Alphabetical sorting */
j=0 ; num_str= '' ; rnd_s= ''
do while j < 36
num= random(0,35)
if wordpos(num,num_str) = 0 then do
j= j + 1
num_str= num_str||num||' '
if num >= 10 then do
num= num - 10 + x2d(41)
num= d2c(num)
end
rnd_s= rnd_s||num
end
end /* say 'Generated string: '||rnd_s||eol */
say 'Polybius square:'||eol
call tab cyph, rnd_s ,1
say "Only characters from the '"|| msg||"'"||eol
t= translate(rnd_s,' ',msg)
_t= translate(rnd_s,' ',t)
call tab cyph, _t ,1
len_c= length(cyph) ; cyph_T=''
do i=1 to len_c
ch_i= substr(cyph,i,1)
do j=1 to len_c
ch_j= substr(cyph,j,1)
cyph_T= cyph_T||ch_i||ch_j||' '
end
end
enc_msg= ''
do i=1 to length(msg)
ch= substr(msg,i,1)
j= pos(ch,rnd_s,1)
enc_msg= enc_msg||word(cyph_T,j)
end
say "Conversion by table: "||eol||eol||msg||" ==> "||enc_msg||eol
call tab keyword, enc_msg
len= length(keyword)
n_row= 0 ; column.= ''
do while enc_msg <> ''
parse var enc_msg 1 s1 +(len) enc_msg
n_row= n_row+1
do m= 1 to len
ch_m= substr(s1,m,1)
column.m= column.m||ch_m
end
end
s_lst= ''
do m= 1 to len
ch= substr(new_key,m,1)
i= pos(ch,keyword,1)
w_i= column.i
s_lst= s_lst||w_i||' '
end
row.= '' ; t_row= ''
do i=1 to len
w_i= word(s_lst,i)
do j=1 to n_row
row.j= row.j||substr(w_i,j,1)
end
end
do j=1 to n_row; t_row= t_row||row.j; end
say "Sorted by columns:"||eol
call tab new_key, t_row
say '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
say 'Encrypted message: '||s_lst
say '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~'
exit
tab:
parse arg h, s, p /* header, string, param */
lh= length(h) ;
s= h||copies('-',lh)||s ; ls= length(s)
h=' -'||h
t= '' ; j= 1
do i= 1 to ls by lh
row= substr(s,i,lh)
r_ch= ''
do l=1 to lh
ch= substr(row,l,1)
r_ch= r_ch||ch||' '
end
row= r_ch
if p <> '' then row= row||'|'||substr(h,j,1)
t= t||row||eol
j= j + 1
end
say t
return
- Output:
Polybius square: A D F G V X | - - - - - - |- D H 8 P U 4 |A E T S C 5 9 |D A 3 Z F R I |F Y O 1 Q 7 W |G J 2 G V N X |V L 6 0 K M B |X Only characters from the 'ATTACKAT1200AM' A D F G V X | - - - - - - |- |A T C |D A |F 1 |G 2 |V 0 K M |X Conversion by table: ATTACKAT1200AM ==> FADDDDFADGXGFADDGFVDXFXFFAXV L I F E G U A R D - - - - - - - - - F A D D D D F A D G X G F A D D G F V D X F X F F A X V Sorted by columns: A D E F G I L R U - - - - - - - - - F D D D D A F A D D F F G A X G G D F X F X X D V A F V ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Encrypted message: FDF DFX DFF DGX DAX AXD FGVV AGA DDF ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~