ADFGVX cipher

From Rosetta Code
Task
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

Translation of: Nim
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

AArch64 Assembly

Works with: as version Raspberry Pi 3B version Buster 64 bits
/* ARM assembly AARCH64 Raspberry PI 3B */
/*  program adfgvx64.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 this file see task include a file in language AArch64 assembly*/
.include "../includeConstantesARM64.inc" 

.equ SIZE,   6
.equ SIZEC,  SIZE * SIZE
.equ KEYSIZE,   9
.equ FSTAT,        80
.equ O_RDWR,  0x0002         // open for reading and writing

/*******************************************/
/* Structures                          **/
/*******************************************/
/* structure de type   stat 64 bits : infos fichier  */
    .struct  0
Stat_dev_t:                  // ID of device containing file
    .struct Stat_dev_t + 8
Stat_ino_t:                  // inode
    .struct Stat_ino_t + 4
Stat_mode_t:                 // File type and mode
    .struct Stat_mode_t + 4
Stat_nlink_t:                // Number of hard links
    .struct Stat_nlink_t + 4
Stat_uid_t:                  // User ID of owner
    .struct Stat_uid_t + 8
Stat_gid_t:                  // Group ID of owner
    .struct Stat_gid_t + 8   
Stat_rdev_t:                 // Device ID (if special file)
    .struct Stat_rdev_t + 8
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 64 bits start.\n"
szCarriageReturn:     .asciz "\n"
.align 4

qGraine:  .quad  1234567         // random init

/*********************************/
/* UnInitialized data            */
/*********************************/
.bss
sKeyWord:              .skip 16
sKeyWordSorted:        .skip 16
tabPolybius:           .skip SIZE * SIZE + 4
sBuffer:               .skip 1000
sBuffex1:              .skip 1000
sBuffex2:              .skip 1000
tabPosit:              .skip 16
tabPositInv:           .skip 16
/*********************************/
/*  code section                 */
/*********************************/
.text
.global main 
main:                            // entry of program 
    ldr x0,qAdrszMessStart
    bl affichageMess
    bl createPolybius            // create 6*6 polybius

    ldr x0,qAdrsKeyWord
    bl generateKey               // generate key
    cmp x0,#-1                   // file error ?
    beq 100f
    bl affichageMess             // display key
    ldr x0,qAdrszCarriageReturn
    bl affichageMess
    
    ldr x0,qAdrszMessCryptText
    bl affichageMess
    ldr x0,qAdrszText             // text encrypt
    ldr x1,qAdrtabPolybius
    ldr x2,qAdrsKeyWord
    ldr x3,qAdrsBuffer            // result buffer
    bl encryption
    cmp x0,#-1                    // error if unknow character in text
    beq 100f
    bl affichageMess              // display text encrypted 
    ldr x0,qAdrszCarriageReturn
    bl affichageMess
    ldr x0,qAdrszCarriageReturn
    bl affichageMess
 
    ldr x0,qAdrszMessDecryptText
    bl affichageMess
    ldr x0,qAdrsBuffer            // text decrypt 
    ldr x1,qAdrtabPolybius
    ldr x2,qAdrsKeyWord
    ldr x3,qAdrsBuffex1           // result buffer
    bl decryption
    bl affichageMess
    ldr x0,qAdrszCarriageReturn
    bl affichageMess
 
100:                             // standard end of the program 
    mov x0, #0                   // return code
    mov x8, #EXIT                // request to exit program
    svc #0                       // perform the system call
    
qAdrszCarriageReturn:        .quad  szCarriageReturn
qAdrszMessDecryptText:       .quad  szMessDecryptText
qAdrszMessCryptText:         .quad  szMessCryptText
qAdrszMessStart:             .quad  szMessStart
qAdrsKeyWord:                .quad  sKeyWord
qAdrszText:                  .quad  szText
/***************************************************/
/*   create 6 * 6 polybius                    */
/***************************************************/
createPolybius:
    stp x1,lr,[sp,-16]!          // save registers
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]! 
    ldr x0,qAdrszListChar        // character list address
    mov x1,#LGLISTCHAR           // character list size
    ldr x2,qAdrtabPolybius
    bl shufflestrings            // shuffle list
    ldr x0,qAdrszMessPolybius
    bl affichageMess
    ldr x0,qAdrszTitle           // display polybius lines
    bl affichageMess
    ldr x0,qAdrszLine1
    mov x3,#0
    mov x4,#4
1:
    ldrb w1,[x2,x3]
    strb w1,[x0,x4]
    add x4,x4,#2
    add x3,x3,#1
    cmp x3,#SIZE
    blt 1b
    bl affichageMess
    ldr x0,qAdrszLine2
    mov x3,#SIZE
    mov x4,#4
2:
    ldrb w1,[x2,x3]
    strb w1,[x0,x4]
    add x4,x4,#2
    add x3,x3,#1
    cmp x3,#SIZE * 2
    blt 2b
    bl affichageMess
    ldr x0,qAdrszLine3
    mov x3,#SIZE * 2
    mov x4,#4
3:
    ldrb w1,[x2,x3]
    strb w1,[x0,x4]
    add x4,x4,#2
    add x3,x3,#1
    cmp x3,#SIZE * 3
    blt 3b
    bl affichageMess
    ldr x0,qAdrszLine4
    mov x3,#SIZE * 3
    mov x4,#4
4:
    ldrb w1,[x2,x3]
    strb w1,[x0,x4]
    add x4,x4,#2
    add x3,x3,#1
    cmp x3,#SIZE * 4
    blt 4b
    bl affichageMess
    ldr x0,qAdrszLine5
    mov x3,#SIZE * 4
    mov x4,#4
5:
    ldrb w1,[x2,x3]
    strb w1,[x0,x4]
    add x4,x4,#2
    add x3,x3,#1
    cmp x3,#SIZE * 5
    blt 5b
    bl affichageMess
    ldr x0,qAdrszLine6
    mov x3,#SIZE * 5
    mov x4,#4
6:
    ldrb w1,[x2,x3]
    strb w1,[x0,x4]
    add x4,x4,#2
    add x3,x3,#1
    cmp x3,#SIZE * 6
    blt 6b
    bl affichageMess
 
100:
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16 
    ret 
qAdrszListChar:           .quad  szListChar
qAdrtabPolybius:          .quad  tabPolybius
qAdrszMessPolybius:       .quad  szMessPolybius
qAdrszTitle:              .quad  szTitle
qAdrszLine1:              .quad  szLine1
qAdrszLine2:              .quad  szLine2
qAdrszLine3:              .quad  szLine3
qAdrszLine4:              .quad  szLine4
qAdrszLine5:              .quad  szLine5
qAdrszLine6:              .quad  szLine6
/***************************************************/
/*  generate key word                              */
/***************************************************/
/* x0  key word address */
generateKey:
    stp x1,lr,[sp,-16]!
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]!
    stp x6,x7,[sp,-16]!
    stp x8,x9,[sp,-16]!
    stp x10,x11,[sp,-16]!
    stp x12,x13,[sp,-16]!
    mov x9,x0
    mov x0,AT_FDCWD
    ldr x1,qAdrszFileName    // file name
    mov x2,#O_RDWR           // flags
    mov x3,#0                // mode 
    mov x8,#OPEN             // file open
    svc 0 
    cmp x0,#0                // error ?
    ble 99f
    mov x11,x0               // FD save
    ldr x1,qAdrsBuffer       // buffer address
    mov x8, #FSTAT           // call systeme NEWFSTAT
    svc 0                  
    cmp x0,#0
    blt 98f
                             //  load file size
    ldr x1,qAdrsBuffer       // buffer address
    ldr w6,[x1,#Stat_size_t] // file size
    //ldr w6,[x1,mbox_data_size]
    lsr x12,x6,#5            // align size to multiple 16 for stack alignement
    lsl x12,x12,#5
    add x12,x12,#32          // add for great buffer
    sub sp,sp,x12            // reserve buffer on stack
    mov fp,sp                // address save 
    mov x0,x11
    mov x1,fp
    mov x2,x12
    mov x8,#READ             // call system read file
    svc 0 
    cmp x0,#0                // error read ?
    blt 97f
    mov x0,x11
    mov x8,#CLOSE            // call system close file
    svc 0 
    cmp x0,#0                // error close ?
    blt 96f
    sub sp,sp,#0x1000        // create array word address on stack
    mov x10,sp               // save address array
    mov x1,#0
    mov x2,fp
    mov x5,#0                // index word ok 
    mov x3,#0                // word length
1:
    ldrb w4,[fp,x1]          // load character
    cmp w4,#0x0D             // end word ?
    beq 2f                   // yes
    add x1,x1,#1
    add x3,x3,#1
    b 1b
2:
    cmp x3,#KEYSIZE          // word length = key length ?
    bne 3f                   // no ?
    mov x0,x2
    bl wordControl           // contril if all letters are différent ?
    cmp x0,#1
    bne 3f
    str x2,[x10,x5,lsl #3]   // if ok store word address in array on stack
    add x5,x5,#1             // increment word counter
3:
    add x1,x1,#2
    cmp x1,x6                // end ?
    beq 4f
    add x2,fp,x1             // new word begin
    mov x3,#0                // init word length
    b 1b                     // and loop   
4:
    mov x0,x5                // number random to total words
    bl genereraleas 
    ldr x2,[x10,x0,lsl #3]   // load address word 
    mov x1,#0
5:                           // copy random word in word result
    ldrb w3,[x2,x1]
    strb w3,[x9,x1]
    add x1,x1,#1
    cmp x1,#KEYSIZE
    blt 5b
    strb wzr,[x9,x1]         // zero final
    mov x0,x9
    b 100f
                             // display errors
96:
    ldr x0,qAdrszMessClose
    bl affichageMess     
    mov x0,#-1               // error
    b 100f
97:
    ldr x0,qAdrszMessRead
    bl affichageMess 
    mov x0,#-1               // error
    b 100f
98:
    ldr x0,qAdrszMessStat
    bl  affichageMess    
    mov x0,#-1               // error
    b 101f
99:
    ldr x0,qAdrszMessOpen
    bl  affichageMess    
    mov x0,#-1               // error
    b 101f
100:
    add sp,sp,x12
    add sp,sp,#0x1000
101:
    ldp x12,x13,[sp],16
    ldp x10,x11,[sp],16
    ldp x8,x9,[sp],16
    ldp x6,x7,[sp],16
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16
    ret 
qAdrszFileName:     .quad  szFileName
qAdrszMessOpen:     .quad  szMessOpen
qAdrszMessRead:     .quad  szMessRead
qAdrszMessStat:     .quad  szMessStat
qAdrszMessClose:    .quad  szMessClose
qAdrsBuffer:        .quad  sBuffer
/******************************************************************/
/*     control if letters are diferents                  */ 
/******************************************************************/
/* x0 contains the address of the string */
/* x0 return 1 if Ok else return 0 */
wordControl:
    stp x1,lr,[sp,-16]!
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]!
    mov x1,#0                 // init index 1
1:
    ldrb w3,[x0,x1]           // load one character
    cmp x3,#0x0D              // end word ?
    mov x5,#1
    csel x0,x5,x0,eq          // yes is ok 
    //moveq x0,#1               // yes is ok 
    beq 100f                  // -> end
    add x2,x1,#1              // init index two
2:
    ldrb w4,[x0,x2]           // load one character
    cmp w4,#0x0D              // end word ?
    add x5,x1,1
    csel x1,x5,x1,eq          // yes increment index 1
    beq 1b                    // and loop1
    cmp x3,x4                 // caracters equals ?
    csel x0,xzr,x0,eq         // yes is not good
    beq 100f                  // and end
    add x2,x2,#1              // else increment index 2
    b 2b                      // and loop 2
100:
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16
    ret 
/******************************************************************/
/*         key sort by insertion sort                                              */ 
/******************************************************************/
/* x0 contains the address of String */
/* x1 contains the first element    */
/* x2 contains the number of element */
/* x3 contains result address */
keySort:
    stp x1,lr,[sp,-16]!
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]!
    stp x6,x7,[sp,-16]!
    stp x8,x9,[sp,-16]!
    stp x10,x11,[sp,-16]!
    ldr x7,qAdrtabPosit
    mov x10,x3
    mov x3,#0
0:                            // init position array and copy key
    strb w3,[x7,x3]           // in result array 
    ldrb w4,[x0,x3]
    strb w4,[x10,x3]
    add x3,x3,#1
    cmp x3,#KEYSIZE
    blt 0b
    
    add x3,x1,#1              // start index i
1:                            // start loop
    ldrb w4,[x10,x3]          // load value A[i]
    ldrb w8,[x7,x3]           // load position
    sub x5,x3,#1              // index j
2:
    ldrb w6,[x10,x5]          // load value A[j]
    ldrb w9,[x7,x5]           // load position
    cmp x6,x4                 // compare value
    ble 3f
    add x5,x5,#1                 // increment index j
    strb w6,[x10,x5]          // store value A[j+1]
    strb w9,[x7,x5]           // store position
    subs x5,x5,#2                // j = j - 1
    bge 2b                    // loop if j >= 0
3:
    add x5,x5,#1              // increment index j
    strb w4,[x10,x5]          // store value A[i] in A[j+1]
    strb w8,[x7,x5]
    add x3,x3,#1                 // increment index i
    cmp x3,x2                 // end ?
    blt 1b                    // no -> loop
    
    ldr x1,qAdrtabPositInv    // inverse position
    mov x2,#0                 // index
4:
    ldrb w3,[x7,x2]           // load position index
    strb w2,[x1,x3]           // store index in position
    add x2,x2,#1              // increment index
    cmp x2,#KEYSIZE           // end ?
    blt 4b
    mov x0,x10
100:
    ldp x10,x11,[sp],16
    ldp x8,x9,[sp],16
    ldp x6,x7,[sp],16
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16                 // TODO: retaur à completer 
    ret 
qAdrtabPosit:        .quad  tabPosit
qAdrtabPositInv:     .quad  tabPositInv
/******************************************************************/
/*         text encryption                                        */ 
/******************************************************************/
/* x0 contains the address of text */
/* x1 contains polybius address
/* x2 contains the key address   */
/* x3 contains result buffer address */
encryption:
    stp x1,lr,[sp,-16]!
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]!
    stp x6,x7,[sp,-16]!
    stp x8,x9,[sp,-16]!
    stp x10,x11,[sp,-16]!
    mov x9,x0                  // save text address
    mov x8,x3
    mov x10,x1                 // save address polybius
    mov x0,x2                  // key address 
    mov x1,#0                  // first character
    mov x2,#KEYSIZE            // key length
    ldr x3,qAdrsKeyWordSorted  // result address
    bl keySort                 // sort leters of key 
   // bl affichageMess         // if you want display sorted key
  //  ldr x0,qAdrszCarriageReturn
  //  bl affichageMess
    ldr x3,qAdrsBuffex1
    mov x5,#0                  // init text index
    mov x4,#0                  // init result index
1:
    ldrb w0,[x9,x5]            // load a byte to text
    cmp x0,#0                  // end ?
    beq 4f
    mov x6,#0                  // init index polybius
2:
    ldrb w7,[x10,x6]           // load character polybius
    cmp x7,x0                  // equal ?
    beq 3f
    add x6,x6,#1               // increment index
    cmp x6,#SIZEC              // not find -> error 
    bge 99f
    b 2b                       // and loop
3:
    mov x0,x6
    bl convPosCode             // convert position in code character
    strb w0,[x3,x4]            // line code character
    add x4,x4,#1
    strb w1,[x3,x4]            // column code character
    add x4,x4,#1
    
    add  x5,x5,#1              // increment text index
    b 1b
4:
    mov x0,#0                  // zero final -> text result
    strb w0,[x3,x4]
    mov x5,x3                  
    mov x1,#0                  // index position column 
    mov x7,#0                  // index text
    ldr x2,qAdrtabPositInv 
5:
    ldrb w0,[x2,x1]           // load position text
7:                            // loop to characters transposition
    
    ldrb w6,[x5,x0]           // load character 
    strb w6,[x8,x7]           // store position final
    add x7,x7,#1              // increment final index
    add x0,x0,#KEYSIZE        // add size key
    cmp x0,x4                 // end ?
    blt 7b
    add x1,x1,#1              // add index column
    cmp x1,#KEYSIZE           // < key size
    blt 5b                    // yes -> loop

    mov x6,#0                 // zero final
    strb w6,[x8,x7]
    mov x0,x8                 // return address encrypted text

    b 100f
99:                           // display error
    ldr x0,qAdrszMessErrorChar
    bl affichageMess
    mov x0,#-1
100:
    ldp x10,x11,[sp],16
    ldp x8,x9,[sp],16
    ldp x6,x7,[sp],16
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16
    ret 
qAdrsBuffex1:        .quad  sBuffex1
qAdrsKeyWordSorted:  .quad  sKeyWordSorted
qAdrszMessErrorChar: .quad  szMessErrorChar
/******************************************************************/
/*         text decryption                                              */ 
/******************************************************************/
/* x0 contains the address of text */
/* x1 contains polybius address
/* x2 contains the key    */
/* x3 contains result buffer */
/* x0 return decoded text */
decryption:
    stp x1,lr,[sp,-16]!
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]!
    stp x6,x7,[sp,-16]!
    stp x8,x9,[sp,-16]!
    stp x10,x11,[sp,-16]!
    stp x12,x13,[sp,-16]!
    mov x4,#0
1:                              // compute text length
    ldrb w5,[x0,x4]
    cmp x5,#0
    add x11,x4,1
    csel x4,x11,x4,ne
    bne 1b
    mov x12,x0
    mov x11,x1
    mov x10,x2
    mov x13,x3
                                 // compute line number and remainder
    mov x1,#KEYSIZE              // compute line number and remainder
    udiv x8,x4,x1                // line number
    msub x7,x8,x1,x4             // remainder characters last line
    mov x0,x10                   // key address
    mov x1,#0                    // first character
    mov x2,#KEYSIZE              // size
    ldr x3,qAdrsKeyWordSorted    // result address
    bl keySort                   // sort key
    ldr x10,qAdrtabPositInv      // inverse position
    mov x2,#0                    // index colonne tabposit
    mov x5,#0                    // text index
    mov x0,#0                    // index line store text
    mov x1,#0                    // counter line
    
    ldr x9,qAdrsBuffex2
1:
    ldrb w3,[x10,x2]             // load position
    ldrb w6,[x12,x5]             // load text character  
    add x3,x3,x0                 // compute position with index line
    strb w6,[x9,x3]              // store character in good position 
    
    add x5,x5,#1                 // increment index text
    cmp x5,x4                    // end ?
    bge 4f
    add x1,x1,#1                 // increment line 
    cmp x1,x8                    // line < line size
    blt 2f
    bgt 11f                      // line = line size
    sub x3,x3,x0                 // restaure position column
    cmp x3,x7                    // position < remainder  so add character other line
    blt 2f
11:
    mov x1,#0                    // init ligne
    mov x0,#0                    // init line shift
    add x2,x2,#1                 // increment index array position inverse
    cmp x2,#KEYSIZE              // end ?
    csel x2,xzr,x2,ge            // init index
    b 3f
2:
    add x0,x0,#KEYSIZE
3:
    b 1b
4:                               // convertir characters with polybius
    mov x3,#0
    mov x5,#0

5:
    mov x0,x11
    ldrb w1,[x9,x3]              // load a first character
    add x3,x3,#1
    ldrb w2,[x9,x3]              // load a 2ieme character 
    bl decodPosCode              // decode
    strb w0,[x13,x5]              // store result in final result
    add x5,x5,#1                 // increment final result index
    add x3,x3,#1                 // increment index text
    cmp x3,x4                    // end ?
    blt 5b
    mov x0,#0                    // final zero
    strb w0,[x13,x5]
    mov x0,x13                    // return final result address
100:
    ldp x12,x13,[sp],16
    ldp x10,x11,[sp],16
    ldp x8,x9,[sp],16
    ldp x6,x7,[sp],16
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16                 // TODO: retaur à completer 
    ret 
qAdrsBuffex2:      .quad    sBuffex2
/******************************************************************/
/*         convertir position en code                                              */ 
/******************************************************************/
/* x0 contains the position in polybius */
/* x0 return code1 */
/* x1 return code2 */
convPosCode:
    stp x2,lr,[sp,-16]!
    stp x3,x4,[sp,-16]! 
    ldr x4,qAdrszListCharCode
    mov x1,#SIZE
    udiv x2,x0,x1
    msub x3,x2,x1,x0
    //bl division
    ldrb w0,[x4,x2]
    ldrb w1,[x4,x3]
100:
    ldp x3,x4,[sp],16
    ldp x2,lr,[sp],16
    ret 
qAdrszListCharCode:   .quad  szListCharCode
/******************************************************************/
/*         convertir code en character                                              */ 
/******************************************************************/
/* x0  polybius address */
/* x1 code 1 */
/* x2 code 2 */
/* x0 return character */
decodPosCode:
    stp x1,lr,[sp,-16]!
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]! 
    ldr x4,qAdrszListCharCode
    mov x3,#0
1:
    ldrb w5,[x4,x3]
    cmp x5,#0
    beq 2f
    cmp x5,x1
    csel x1,x3,x1,eq
    cmp x5,x2
    csel x2,x3,x2,eq
    add x3,x3,#1
    b 1b
2:
    mov x5,#SIZE
    mul x1,x5,x1
    add x1,x1,x2
    ldrb w0,[x0,x1]
100:
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16
    ret 

/******************************************************************/
/*     shuffle strings  algorithme Fisher-Yates                   */ 
/******************************************************************/
/* x0 contains the address of the string */
/* x1 contains string length */
/* x2 contains address result string */
shufflestrings:
    stp x1,lr,[sp,-16]!               // TODO: save à completer 
    stp x2,x3,[sp,-16]! 
    stp x4,x5,[sp,-16]!
    mov x3,#0
1:                            // loop copy string in result
    ldrb w4,[x0,x3]
    strb w4,[x2,x3]
    add x3,x3,#1
    cmp x3,x1
    ble 1b
    sub x1,x1,#1              // last element
2:
    mov x0,x1
    bl genereraleas           // call random 
    ldrb w4,[x2,x1]           // load byte string index loop
    ldrb w3,[x2,x0]           // load byte string random index 
    strb w3,[x2,x1]           // and exchange
    strb w4,[x2,x0]
    subs x1,x1,#1
    cmp x1,#1
    bge 2b

100:
    ldp x4,x5,[sp],16
    ldp x2,x3,[sp],16
    ldp x1,lr,[sp],16
    ret 
    
/***************************************************/
/*   Generation random number                  */
/***************************************************/
/* x0 contains limit  */
genereraleas:
    stp x1,lr,[sp,-16]!            // save  registers
    stp x2,x3,[sp,-16]!            // save  registers
    ldr x1,qAdrqGraine
    ldr x2,[x1]
    ldr x3,qNbDep1
    mul x2,x3,x2
    ldr x3,qNbDep2
    add x2,x2,x3
    str x2,[x1]                    // maj de la graine pour l appel suivant 
    cmp x0,#0
    beq 100f
    udiv x3,x2,x0
    msub x0,x3,x0,x2               // résult = remainder
 
100:                               // end function
    ldp x2,x3,[sp],16              // restaur  2 registers
    ldp x1,lr,[sp],16              // restaur  2 registers
    ret                            // return to address lr x30
qAdrqGraine: .quad qGraine
qNbDep1:     .quad 0x0019660d
qNbDep2:     .quad 0x3c6ef35f

/***************************************************/
/*      ROUTINES INCLUDE                           */
/***************************************************/
.include "../includeARM64.inc"
Output:
Program 64 bits start.
6 x 6 Polybius square:
  | A D F G V X
---------------
A | Q A M C 0 X
D | 1 S O Y R E
F | H 7 J T U W
G | V F 3 Z 4 I
V | 8 B 9 K 6 2
X | P D L G 5 N
hailstorm
Encrypted text :
DVXAGVFFGAGAVDDDAAAGFVAGAFDA

Decrypted text :
ATTACKAT1200AM

ARM Assembly

Works with: as version Raspberry Pi
/* 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

C++

#include <algorithm>
#include <cstdint>
#include <fstream>
#include <iostream>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_set>
#include <vector>

const std::string ADFGVX = "ADFGVX";
const std::string ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

std::random_device random;
std::mt19937 mersenne_twister(random());

std::vector<std::vector<char>> initialise_polybius_square() {
	std::vector<char> letters(ALPHABET.begin(), ALPHABET.end());
	std::shuffle(letters.begin(), letters.end(), mersenne_twister);

	std::vector<std::vector<char>> result = { 6, std::vector<char>(6, 0) };
	for ( int32_t row = 0; row < 6; ++row ) {
		for ( int32_t column = 0; column < 6; ++column ) {
			result[row][column] = letters[6 * row + column];
		}
	}
	return result;
}

// Create a key using a word from the dictionary 'unixdict.txt'
std::string create_key(const uint64_t& size) {
	if ( size < 7 || size > 12 ) {
		throw std::invalid_argument("Key should contain between 7 and 12 letters, both inclusive.");
	}

	std::vector<std::string> candidates;
	std::fstream file_stream;
	file_stream.open("../unixdict.txt");
	std::string word;
	while ( file_stream >> word ) {
		if ( word.length() == size &&
			word.length() == std::unordered_set<char>{ word.begin(), word.end() }.size() ) {
			std::transform(word.begin(), word.end(), word.begin(), [](const char& ch){ return std::toupper(ch); });
			if ( word.find_first_not_of(ALPHABET) == std::string::npos ) {
				candidates.emplace_back(word);
			}
		}
	}
	std::shuffle(candidates.begin(), candidates.end(), mersenne_twister);
	std::string key = candidates[0];
	return key;
}

std::string encrypt(const std::string& plain_text,
					const std::vector<std::vector<char>>& polybius,
					const std::string& key) {
	std::string code = "";
	for ( const char& ch : plain_text ) {
		for ( int32_t row = 0; row < 6; ++row ) {
			for ( int32_t column = 0; column < 6; ++column ) {
				if ( polybius[row][column] == ch ) {
					code += ADFGVX[row];
					code += ADFGVX[column];
				}
			}
		}
	}

	std::string encrypted = "";
	for ( const char& ch : key ) {
		for ( uint64_t i = key.find(ch); i < code.length(); i += key.length() ) {
			encrypted += code[i];
		}
		encrypted += " ";
	}
	return encrypted;
}

std::string decrypt(const std::string& encrypted_text,
					const std::vector<std::vector<char>>& polybius,
					const std::string& key) {
	const uint64_t space_count = std::count(encrypted_text.begin(), encrypted_text.end(), ' ');
	const uint64_t code_size = encrypted_text.length() - space_count;

	std::vector<std::string> blocks;
	std::stringstream stream(encrypted_text);
	std:: string word;
	while ( stream >> word ) {
	    blocks.emplace_back(word);
	}

	std::string code = "";
	for ( int32_t i = 0; code.length() < code_size; ++i ) {
		for ( const std::string& block : blocks ) {
			if ( code.length() < code_size ) {
				code += block[i];
			}
		}
	}

	std::string plain_text = "";
	for ( uint64_t i = 0; i < code_size - 1; i += 2 ) {
		int32_t row = ADFGVX.find(code[i]);
		int32_t column = ADFGVX.find(code[i + 1]);
		plain_text += polybius[row][column];
	}
	return plain_text;
}

int main() {
	const std::vector<std::vector<char>> polybius = initialise_polybius_square();
	std::cout << "The 6 x 6 Polybius square:" << std::endl;
	std::cout << " | A D F G V X" << std::endl;
	std::cout << "--------------" << std::endl;
	for ( int32_t row = 0; row < 6; ++row ) {
		std::cout << ADFGVX[row] << "|";
		for ( int32_t column = 0; column < 6; ++column ) {
			std::cout << " " << polybius[row][column];
		}
		std::cout << std::endl;
	}
	std::cout << std::endl;

	const std::string key = create_key(9);
	std::cout << "The key is " << key << std::endl << std::endl;
	const std::string plain_text = "ATTACKAT1200AM";
	std::cout << "Plain text: " << plain_text <<std::endl << std::endl;
	const std::string encrypted_text = encrypt(plain_text, polybius, key);
	std::cout << "Encrypted: " << encrypted_text << std::endl << std::endl;
	const std::string decrypted_text = decrypt(encrypted_text, polybius, key);
	std::cout << "Decrypted: " << decrypted_text << std::endl;
}
Output:
The 6 x 6 Polybius square:
 | A D F G V X
--------------
A| X Q I Z 1 K
D| A O Y 2 S 5
F| G 7 J R 9 T
G| 0 3 E P C V
V| N 6 D L H B
X| U 4 F W M 8

The key is HORSETAIL

Plain text: ATTACKAT1200AM

Encrypted: DVDV AAG FXG XDA FAG XFA DXD AAA GVX 

Decrypted: 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

Translation of: Wren
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.)

Java

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public final class ADFGVXCipher {

	public static void main(String[] args) throws IOException {	
		final char[][] polybius = initialisePolybiusSquare();
		System.out.println("The 6 x 6 Polybius square:");
		System.out.println(" | A D F G V X");
		System.out.println("--------------");
		for ( int row = 0; row < 6; row++ ) {
			System.out.print(ADFGVX.charAt(row) + "|");
			for ( int column = 0; column < 6; column++ ) {
				System.out.print(" " + polybius[row][column]);
			}
			System.out.println();
		}
		System.out.println();

		final String key = createKey(9);
		System.out.println("The key is " + key);
		System.out.println();
		final String plainText = "ATTACKAT1200AM";
		System.out.println("Plain text: " + plainText);
		System.out.println();
		final String encryptedText = encrypt(plainText, polybius, key);
		System.out.println("Encrypted: " + encryptedText);
		System.out.println();
		final String decryptedText = decrypt(encryptedText, polybius, key);
		System.out.println("Decrypted: " + decryptedText);		
	}
	
	private static String encrypt(String plainText, char[][] polybius, String key) {
		String code = "";
		for ( char ch : plainText.toCharArray() ) {
			for ( int row = 0; row < 6; row++ ) {
				for ( int column = 0; column < 6; column++ ) {
					if ( polybius[row][column] == ch ) {						
						code += ADFGVX.charAt(row) + "" + ADFGVX.charAt(column);
					}
				}
			}
		}
		
		String encrypted = "";
		for ( char ch : key.toCharArray() ) {
			for ( int i = key.indexOf(ch); i < code.length(); i += key.length() ) {
				encrypted += code.charAt(i);
			}
			encrypted += " ";
		}
		return encrypted;
	}
	
	private static String decrypt(String encryptedText, char[][] polybius, String key) {
		final int codeSize = encryptedText.replace(" ", "").length();		
		String code = "";
		for ( int i = 0; code.length() < codeSize; i++ ) {
			for ( String block : encryptedText.split(" ") ) {
				if ( code.length() < codeSize ) {
					code += block.charAt(i);
				}
			}
		}
		
		String plainText = "";
		for ( int i = 0; i < codeSize - 1; i += 2 ) {
		    int row = ADFGVX.indexOf(code.substring(i, i + 1));
		    int column = ADFGVX.indexOf(code.substring(i + 1, i + 2));
		    plainText += polybius[row][column];
		}		
		return plainText;	
	}
	
	// Create a key using a word from the dictionary 'unixdict.txt'
	private static String createKey(int size) throws IOException {
		if ( size < 7 || size > 12 ) {
			throw new AssertionError("Key should contain between 7 and 12 letters, both inclusive.");
		}
			  
		List<String> candidates = Files.lines(Path.of("unixdict.txt"))
			.filter( word -> word.length() == size )
			.filter( word -> word.chars().distinct().count() == word.length() )
			.filter( word -> word.chars().allMatch(Character::isLetterOrDigit) )
			.collect(Collectors.toList());
		Collections.shuffle(candidates);
		return candidates.get(0).toUpperCase();		
	}
	
	private static char[][] initialisePolybiusSquare() {
		List<String> letters = Arrays.asList("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""));
		Collections.shuffle(letters);
	
		char[][] result = new char[6][6];
		for ( int row = 0; row < 6; row++ ) {
		    for ( int column = 0; column < 6; column++ ) {
		        result[row][column] = letters.get(6 * row + column).charAt(0);
		    }
		}
		return result;
	}
	
	private static final String ADFGVX = "ADFGVX";	

}
Output:
The 6 x 6 Polybius square:
 | A D F G V X
--------------
A| 2 K 4 Q 9 8
D| W C I M V A
F| 5 S 0 Y B D
G| F T O U J E
V| 6 1 R X 3 G
X| H L Z P 7 N

The key is DISHWATER

Plaintext: ATTACKAT1200AM

Encrypted: DDAG XAA GDF DDF GXF DGF DDD XVX DDD 

Decrypted: ATTACKAT1200AM

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

Translation of: Wren

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

Translation of: Wren
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

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  
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

Library: Wren-ioutil
Library: Wren-seq
Library: Wren-str
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