Description

The Chaocipher was invented by J.F.Byrne in 1918 and, although simple by modern cryptographic standards, does not appear to have been broken until the algorithm was finally disclosed by his family in 2010.

The algorithm is described in this paper by M.Rubin in 2010 and there is a C# implementation here.



Task

Code the algorithm in your language and to test that it works with the plaintext 'WELLDONEISBETTERTHANWELLSAID' used in the paper itself.




-- Chaocipher algorithm by J.F.Byrne 1918.
on chaocipher(input, |key|, mode)
    -- input: text to be enciphered or deciphered.
    -- |key|: script object or record with leftAlpha and rightAlpha properties, each of whose values is a shuffled alphabet text.
    -- mode: the text "encipher" or "decipher".
    script o
        property inputChars : input's characters
        property leftAlpha : |key|'s leftAlpha's characters
        property rightAlpha : |key|'s rightAlpha's characters
        property inAlpha : leftAlpha
        property outAlpha : rightAlpha
        property output : {}
    end script
 
    set alphaLen to (count o's leftAlpha)
    if ((count o's rightAlpha) ≠ alphaLen) then error
    if (mode is "encipher") then
        set {o's inAlpha, o's outAlpha} to {o's rightAlpha, o's leftAlpha}
    else if (mode is not "decipher") then
        error
    end if
    set zenith to 1
    set nadir to alphaLen div 2 + 1
    repeat with char in o's inputChars
        set char to char's contents
        set found to false
        repeat with i from 1 to alphaLen
            if (o's inAlpha's item i = char) then
                set end of o's output to o's outAlpha's item i
                set found to true
                exit repeat
            end if
        end repeat
        if (found) then
            rotate(o's leftAlpha, zenith, alphaLen, -(i - zenith))
            rotate(o's leftAlpha, zenith + 1, nadir, -1)
            rotate(o's rightAlpha, zenith, alphaLen, -i)
            rotate(o's rightAlpha, zenith + 2, nadir, -1)
        end if
    end repeat
 
    return join(o's output, "")
end chaocipher
 
on rotate(theList, l, r, amount)
    set listLength to (count theList)
    if (listLength < 2) then return
    if (l < 0) then set l to listLength + l + 1
    if (r < 0) then set r to listLength + r + 1
    if (l > r) then set {l, r} to {r, l}
    script o
        property lst : theList
        property storage : missing value
    end script
 
    set rangeLength to r - l + 1
    set amount to (rangeLength + rangeLength - amount) mod rangeLength
    if (amount is 0) then return
    set o's storage to o's lst's items l thru (l + amount - 1)
    repeat with i from (l + amount) to r
        set o's lst's item (i - amount) to o's lst's item i
    end repeat
    set j to r - amount
    repeat with i from 1 to amount
        set o's lst's item (j + i) to o's storage's item i
    end repeat
end rotate
 
on join(lst, delim)
    set astid to AppleScript's text item delimiters
    set AppleScript's text item delimiters to delim
    set txt to lst as text
    set AppleScript's text item delimiters to astid
    return txt
end join
 
-- Return a script object containing a couple of randomised alphabets to use as a choacipher key.
on makeKey()
    set lAlpha to "ABCDEFGHIJKLMNOPQRSTUVWXYZ"'s characters
    copy lAlpha to rAlpha
    script |key|
        property leftAlpha : join(shuffle(lAlpha, 1, -1), "")
        property rightAlpha : join(shuffle(rAlpha, 1, -1), "")
    end script
 
    return |key|
end makeKey
 
-- Fisher-Yates (aka Durstenfeld, aka Knuth) shuffle.
on shuffle(theList, l, r)
    set listLength to (count theList)
    if (listLength < 2) then return array
    if (l < 0) then set l to listLength + l + 1
    if (r < 0) then set r to listLength + r + 1
    if (l > r) then set {l, r} to {r, l}
    script o
        property lst : theList
    end script
 
    repeat with i from l to (r - 1)
        set j to (random number from i to r)
        set v to o's lst's item i
        set o's lst's item i to o's lst's item j
        set o's lst's item j to v
    end repeat
 
    return theList
end shuffle
 
-- Demo using the two-alphabet key from the Rubin paper and another generated at random.
-- Decription must be with the key that was used for the encription.
on demo(originalText)
    set key1 to {leftAlpha:"HXUCZVAMDSLKPEFJRIGTWOBNYQ", rightAlpha:"PTLNBQDEOYSFAVZKGJRIHWXUMC"}
    set key2 to makeKey()
    set enciphered to chaocipher(originalText, key1, "encipher")
    set doubleEnciphered to chaocipher(enciphered, key2, "encipher")
    set deDoubleEnciphered to chaocipher(doubleEnciphered, key2, "decipher")
    set deciphered to chaocipher(deDoubleEnciphered, key1, "decipher")
    return join({"Original text = " & originalText, ¬
        "Enciphered = " & enciphered, "Double enciphered = " & doubleEnciphered, ¬
        "De-double enciphered = " & deDoubleEnciphered, "Deciphered  = " & deciphered}, linefeed)
end demo
demo("WELLDONEISBETTERTHANWELLSAID")