AMSCO is an incomplete columnar transposition cipher. A bit to unpack there, but basically that means that you’re putting the message into columns and those columns may not have equal lengths. It was invented by an A.M. Scott in the 19th century, but strangely there is almost nothing online about him.
The AMSCO cipher has two main components. The first is the plain text you wish to encrypt. The second is the numeric key. The key can be a max length of 9 and must contain the numbers 1-n, with n being the length of the key. 1234 and 4132 would both be valid keys, but 1245 would not.
To encrypt text with an AMSCO cipher a table is created with the key as the head and the plain text separated in a specific pattern. It is split in alternating chunks of two and one characters across the rows with the first row starting with two characters and then each row after that alternating between one and two characters. If the key length is even the last chunk size in a row will be equal to the first chunk size in the following row. It is important that the chunk size alternates along the column.
For example, if our key is 4132 and our plain text is “On the other side of the screen, it all looks so easy”:
4 | 1 | 3 | 2 |
---|---|---|---|
on | t | he | o |
t | he | r | si |
de | o | ft | h |
e | sc | r | ee |
ni | t | al | l |
l | oo | k | ss |
oe | a | sy |
If you look at the structure of that table, you’ll see that not all the columns are of the same length. The chunk size has the above mentioned alternating pattern:
4 | 1 | 3 | 2 |
---|---|---|---|
2 | 1 | 2 | 1 |
1 | 2 | 1 | 2 |
2 | 1 | 2 | 1 |
1 | 2 | 1 | 2 |
2 | 1 | 2 | 1 |
1 | 2 | 1 | 2 |
2 | 1 | 2 | 1 |
1 | 2 | 1 |
To encrypt the message, you combine text moving down the columns based on the order of the key. You start with the column “1” (the second column in this example), move on to column “2” (the fourth column), and so on.
The original message, when stripped of punctuation and its text is normalized:
ONTHEOTHERSIDEOFTHESCREENITALLLOOKSSOEASY
The encrypted message:
THEOSCTOOAHERFTRALKSYOSIHEELSSONTDEENILOE
As with any transposition cipher, the frequency count and monographic IC will look like that of plain English. Because this is a transposition cipher, knowing the existence of a single word in the original text could allow us to suss out the entire message, because it could reveal the column structure.
Working on creating some proper code to make the cipher actually usable, but this is a working version of it in Javascript. Requires lodash (npm install lodash).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var _ = require("lodash"); | |
var plainText = "On the other side of the screen it all looks so easy"; | |
var key = "4123"; | |
var cipherText = ""; | |
var cols = []; | |
_.each(key.split(""), function(k) { | |
cols.push({ | |
id: Number(k), | |
chunks: [] | |
}); | |
}); | |
// remove any spaces and convert to upper case. Normallizing the text isn't requierd, | |
// but not doing so would leak information about the text being output | |
var compressedText = plainText.replace(/\s+/g, "").toLocaleUpperCase(); | |
var a = 0; // track position in compressedText | |
var i = 0; // column index | |
var row = 0; | |
var chunkSize = 1; | |
var chunk = ""; | |
var cellMap = [[]]; | |
for (var z=0; z<key.length; z++) { | |
cellMap[z] = []; | |
} | |
// build the columns from the plainText | |
while (a < compressedText.length) { | |
i = i % cols.length; | |
// determine chunk size | |
chunkSize = ((row + i + 1) % 2) + 1; | |
// get a chunk from the plainText | |
chunk = compressedText.substr(a, chunkSize); | |
// add the new chunk to the bottom of the column | |
cols[i].chunks.push(chunk); | |
// move the position tracker | |
a += chunkSize; | |
// add the size of the chunk to our cellMap, which is used later | |
// typically this wouldn't be created when you're building the | |
// plaintext columns, but the data is readily available | |
cellMap[i][row] = chunkSize; | |
i++; | |
if (i === cols.length) { | |
// end of the row, increase row count | |
row++; | |
} | |
} | |
// build the cipherText | |
// go through all of the columns and join them together | |
i = 0; | |
var column = {}; | |
var cipherText = ""; | |
while (i < key.length) { | |
column = _.find(cols, {id : i + 1}); | |
cipherText = cipherText + column.chunks.join(""); | |
i++; | |
} | |
// build some columns | |
// first get the total number of rows | |
// for even key length, this is more straight forward | |
var rowCharCount = 0; | |
var colCount = 0; | |
var rowCount = 0; | |
if (key.length % 2 === 0) { | |
// even number of columns | |
// each row has the same number of letters, except (possible) the last | |
rowCharCount = key.length * 1.5; | |
rowCount = Math.floor(cipherText.length / rowCharCount); | |
if (cipherText.length % rowCharCount > 0) { | |
rowCount++; | |
} | |
} else { | |
rowCount = 0; | |
a = 0; | |
// rowCharCount varies by row | |
// even rows, which start with 2 chars: | |
while (a < cipherText.length) { | |
if (rowCount % 2 === 0) { | |
rowCharCount = key.length + Math.ceil(key.length / 2); | |
} else { | |
rowCharCount = key.length + Math.floor(key.length / 2); | |
} | |
a += rowCharCount; | |
rowCount++; | |
} | |
} | |
// now that the ciphertext has been created | |
// let's attempt to decrypt it using the key | |
// the first step is to create a place to put the values | |
var newCols = []; | |
_.each(key.split(""), function(k) { | |
newCols.push({ | |
id: Number(k), | |
chunks: [] | |
}) | |
}); | |
a = 0; | |
i = 1; | |
row = 0; | |
var colKey = 0; | |
while (a < cipherText.length) { | |
colKey = key.indexOf(i); | |
// utilizing the cellMap we built up earlier. This is | |
// just a table of 2,1,2,1,… values that | |
// represent the chunk size structure | |
rowCount = cellMap[colKey].length; | |
chunkSize = cellMap[colKey][row]; | |
// grab the new column | |
column = _.find(newCols, {id : i}); | |
// get the chunk | |
chunk = cipherText.substr(a, chunkSize); | |
// push the chunk | |
column.chunks.push(chunk); | |
a += chunkSize; | |
row++; | |
if (row === rowCount) { | |
row = 0; | |
i++; | |
} | |
} | |
// build the message back up | |
// this will just go through the columns | |
// in proper order, from left to right and then down | |
// and rebuild the original message | |
a = 0; | |
var finalMessage = ""; | |
i = 0; | |
while (finalMessage.length < compressedText.length) { | |
column = newCols[i]; | |
finalMessage += column.chunks.shift(); | |
i++; | |
// move back to the first column | |
if (i === key.length) { | |
i = 0; | |
} | |
} | |
// let's see how we did | |
console.log(compressedText); | |
console.log(cipherText); | |
console.log(finalMessage); | |
if (compressedText === finalMessage) { | |
console.log("Congrats"); | |
} else { | |
console.log("Failed") | |
} |