ADFGVX cipher

From Rosetta Code
Revision as of 16:48, 15 August 2021 by PureFox (talk | contribs) (Created new draft task, ADFGVX cipher, and added a Wren solution.)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
ADFGVX cipher is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.
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.

Wren

Library: Wren-ioutil
Library: Wren-seq
Library: Wren-str

<lang ecmascript>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)")</lang>

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