Hi. I've developed a bit of an interest in encryption as well as programming in Haskell since I last used this forum. So I decided to build a symmetric block cipher and test its properties to gain more of an understanding of such things. This is for research purposes as they say. I'm not planning to use it with any sensitive data, but I thought it would be interesting to set a little challenge. The code is below together with a description, an example ciphertext and key for demonstration. There is then another ciphertext which contains a Bitcoin address (holding 0.0148 BTC), its private key and some further text. Sorry the prize is so puny but I did say I'm not using sensitive data. So, if anyone is interested in this kind of thing feel free to go for the coin (which will stay there for at least 365 days) and leave any "constructive" comments.

-- Cipher 126 code by Steven Tinsley.

module Main where

import System.IO
import System.Environment
import Data.List
import Data.Bits
import Data.Array.IO

char_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 /!\"£$%^&*()-_=+[]{};:'@#~\\|,.<>.?\n\t`¬¥èéùìòÇØøÅå§ÄÖÑÜ¿öñüà€©®½¾"

dec_bin1 :: Float -> Float -> [Int]
dec_bin1 d_num factor = if (d_num - factor) < 0 then 0 : dec_bin1 d_num (factor / 2)
                        else if (d_num - factor) == 0 then [1]
                        else 1 : dec_bin1 (d_num - factor) (factor / 2)

dec_bin0 :: [Int] -> [Int]
dec_bin0 [] = []
dec_bin0 (x:xs) = pad0 (dec_bin1 (fromIntegral x) 32) ++ dec_bin0 xs

pad0 :: [Int] -> [Int]
pad0 xs = take 6 (xs ++ [0, 0, 0, 0, 0, 0])

pad1 :: [Int] -> [Int]
pad1 xs = take 126 (xs ++ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])

pad2 :: [Char] -> [Char]
pad2 m =
  let l = 21 - mod (length m) 21
      p = "                     "
  in
  m ++ take l p

verify_key :: [Int] -> Int
verify_key [] = 126
verify_key (x:xs) =
  if length (takeWhile (/= x) xs) == length xs then verify_key xs
  else x

-- Converts the plaintext to the base 64 integer form used in the block cipher
conv_txt0 :: [Char] -> [Char] -> [Int]
conv_txt0 [] table = []
conv_txt0 (x:xs) table =
  if length (takeWhile (/= x) table) < 63 then length (takeWhile (/= x) table) : conv_txt0 xs table
  else [63, length (takeWhile (/= x) table) - 63] ++ conv_txt0 xs table

-- Converts the string form of key (2) to the base 126 integer form used in the block cipher
conv_txt1 :: [Char] -> [Char] -> [Int]
conv_txt1 [] table = []
conv_txt1 (x:xs) table =
  let i0 = length (takeWhile (/= x) table)
      i1 = length (takeWhile (/= xs !! 0) table)
  in
  if x == '/' then (63 + i1) : conv_txt1 (drop 1 xs) table
  else i0 : conv_txt1 xs table

-- Converts the string form of key (1) to the base 64 integer form used in the block cipher
conv_txt2 :: [Char] -> [Char] -> [Int]
conv_txt2 [] table = []
conv_txt2 (x:xs) table = length (takeWhile (/= x) table) : conv_txt2 xs table

-- Converts the plaintext to the base 64 string form used in the stream cipher xor operation
conv_txt3 :: [Char] -> [Char] -> [Char]
conv_txt3 [] table = []
conv_txt3 (x:xs) table =
  let i0 = length (takeWhile (/= x) table)
  in
  if i0 > 63 then ['/', table !! (i0 - 63)] ++ conv_txt3 xs table
  else x : conv_txt3 xs table

-- Converts the base 64 string form output by the stream cipher to the base 126 plaintext form
conv_txt4 :: [Char] -> [Char] -> [Char]
conv_txt4 [] table = []
conv_txt4 (x:xs) table = if x == '/' && xs !! 0 == '/' then conv_txt4 (drop 1 xs) table
                         else if x == '/' then table !! (63 + length (takeWhile (/= xs !! 0) table)) : conv_txt4 (drop 1 xs) table
                         else x : conv_txt4 xs table

-- Converts the base 64 integer form output by the block cipher to base 64 string form
conv_seq0 :: [Int] -> [Char] -> [Char]
conv_seq0 [] table = []
conv_seq0 (x:xs) table = (table !! x) : conv_seq0 xs table

conv_seq1 :: [Int] -> [Char] -> [Char]
conv_seq1 [] table = []
conv_seq1 (x0:x1:xs) table =
  if x0 == 63 then (table !! (63 + x1)) : conv_seq1 xs table
  else (table !! x0) : conv_seq1 (x1:xs) table

-- These two functions implement the block cipher logic
sub_char :: [Int] -> [Int] -> [Int]
sub_char [] table = []
sub_char (x:xs) table = (table !! x) : sub_char xs table

swap_bit :: [Int] -> [Int] -> [Int]
swap_bit ptxt [] = []
swap_bit ptxt (x0:x1:x2:x3:x4:x5:xs) = (ptxt !! x0) * 32 + (ptxt !! x1) * 16 + (ptxt !! x2) * 8 + (ptxt !! x3) * 4 + (ptxt !! x4) * 2 + (ptxt !! x5) : swap_bit ptxt xs

conv_txt :: Char -> [Char] -> Int
conv_txt x table = length (takeWhile (/= x) table)

conv_seq :: Int -> [Char] -> Char
conv_seq x table = table !! x

-- These two functions implement the sequence counter used in stream cipher mode
gen_seed1 :: [Int] -> [Int]
gen_seed1 [] = []
gen_seed1 (x0:x1:x2:x3:x4:x5:xs) = (x0 * 32 + x1 * 16 + x2 * 8 + x3 * 4 + x4 * 2 + x5) : gen_seed1 xs

gen_seed0 :: Integer -> Integer -> [Int]
gen_seed0 p 0 = []
gen_seed0 p c = (gen_seed1 (pad1 (dec_bin1 (fromIntegral p) (2 ^ 125)))) ++ gen_seed0 (p + 1) (c - 1)

-- These three functions implement the xor operation used in stream cipher mode
xor2 :: [Int] -> [Int] -> Int
xor2 (x0:x1:x2:x3:x4:x5:xs) (y0:y1:y2:y3:y4:y5:ys) = 32 * (xor x0 y0) + 16 * (xor x1 y1) + 8 * (xor x2 y2) + 4 * (xor x3 y3) + 2 * (xor x4 y4) + (xor x5 y5)

xor1 :: Char -> Char -> Char
xor1 a b = conv_seq (xor2 (pad0 (dec_bin1 (fromIntegral (conv_txt a char_set)::Float) 32)) (pad0 (dec_bin1 (fromIntegral (conv_txt b char_set)::Float) 32))) char_set

xor0 :: [Char] -> [Char] -> [Char]
xor0 [] b = []
xor0 (x:xs) (y:ys) = (xor1 x y) : xor0 xs ys

encrypt1 :: [Int] -> [Int] -> [Int] -> Int -> Int -> [Int]
encrypt1 txt key1 key2 a b =
  if a == b then txt
  else encrypt1 (swap_bit (dec_bin0 (sub_char txt key1)) key2) key1 key2 a (b + 1)

encrypt0 :: [Int] -> [Int] -> [Int] -> Int -> [Char]
encrypt0 [] key1 key2 n = []
encrypt0 p_txt key1 key2 n =
  if length p_txt < 21 then []
  else (conv_seq0 (encrypt1 (take 21 p_txt) key1 key2 n 0) char_set) ++ encrypt0 (drop 21 p_txt) key1 key2 n

decrypt1 :: [Int] -> [Int] -> [Int] -> Int -> Int -> [Int]
decrypt1 txt key1 key2 a b =
  if a == b then txt
  else decrypt1 (sub_char (swap_bit (dec_bin0 txt) key2) key1) key1 key2 a (b + 1)

decrypt0 :: [Int] -> [Int] -> [Int] -> Int -> [Char]
decrypt0 [] key1 key2 n = []
decrypt0 c_txt key1 key2 n =
  if length c_txt < 21 then []
  else (conv_seq0 (decrypt1 (take 21 c_txt) key1 key2 n 0) char_set) ++ decrypt0 (drop 21 c_txt) key1 key2 n

main :: IO ()
main = do
  a <- getArgs
  if (a !! 0) == "s_encrypt" then do
    h0 <- openFile (a !! 1) ReadMode
    hSetEncoding h0 utf8
    message <- hGetContents h0
    h1 <- openFile (a !! 2) ReadMode
    hSetEncoding h1 utf8
    key <- hGetContents h1
    if verify_key (conv_txt2 (take 64 key) char_set) < 126 then do
      putStr "Part 1 of the key is invalid.  Duplication: "
      print (verify_key (conv_txt2 (take 64 key) char_set))
    else if verify_key (conv_txt1 (drop 64 key) char_set) < 126 then do
      putStr "Part 2 of the key is invalid.  Duplication: "
      print (verify_key (conv_txt1 (drop 64 key) char_set))
    else do
      h2 <- openFile (a !! 3) WriteMode
      hSetEncoding h2 utf8
      hPutStr h2 (xor0 (pad2 (conv_txt3 message char_set)) (encrypt0 (gen_seed0 0 (fromIntegral (div (length (pad2 (conv_txt3 message char_set))) 21))) (conv_txt2 (take 64 key) char_set) (conv_txt1 (drop 64 key) char_set) (read (a !! 4)::Int)))
      hClose h2
    hClose h0
    hClose h1
  else if (a !! 0) == "s_decrypt" then do
    h0 <- openFile (a !! 1) ReadMode
    hSetEncoding h0 utf8
    message <- hGetContents h0
    h1 <- openFile (a !! 2) ReadMode
    hSetEncoding h1 utf8
    key <- hGetContents h1
    h2 <- openFile (a !! 3) WriteMode
    hSetEncoding h2 utf8
    hPutStr h2 (conv_txt4 (xor0 message (encrypt0 (gen_seed0 0 (fromIntegral (div (length message) 21))) (conv_txt2 (take 64 key) char_set) (conv_txt1 (drop 64 key) char_set) (read (a !! 4)::Int))) char_set)
    hClose h0
    hClose h1
    hClose h2
  else if (a !! 0) == "b_encrypt" then do
    h0 <- openFile (a !! 1) ReadMode
    hSetEncoding h0 utf8
    message <- hGetContents h0
    h1 <- openFile (a !! 2) ReadMode
    hSetEncoding h1 utf8
    key <- hGetContents h1
    if verify_key (conv_txt2 (take 64 key) char_set) < 126 then do
      putStr "Part 1 of the key is invalid.  Duplication: "
      print (verify_key (conv_txt2 (take 64 key) char_set))
    else if verify_key (conv_txt1 (drop 64 key) char_set) < 126 then do
      putStr "Part 2 of the key is invalid.  Duplication: "
      print (verify_key (conv_txt1 (drop 64 key) char_set))
    else do
      h2 <- openFile (a !! 3) WriteMode
      hSetEncoding h2 utf8
      hPutStr h2 (encrypt0 (conv_txt2 (pad2 (conv_txt3 message char_set)) char_set) (conv_txt2 (take 64 key) char_set) (conv_txt1 (drop 64 key) char_set) (read (a !! 4)::Int))
      hClose h2
    hClose h0
    hClose h1
  else if (a !! 0) == "b_decrypt" then do
    h0 <- openFile (a !! 1) ReadMode
    hSetEncoding h0 utf8
    message <- hGetContents h0
    h1 <- openFile (a !! 2) ReadMode
    hSetEncoding h1 utf8
    key0 <- hGetContents h1
    h2 <- openFile (a !! 3) WriteMode
    block <- newArray (0, 125) 0 :: IO (IOArray Int Int)
    key1_ <- mod_key1 (conv_txt2 (take 64 key0) char_set) block 0
    key1 <- mod_key0 key1_ [] 0 64
    key2_ <- mod_key1 (conv_txt1 (drop 64 key0) char_set) block 0
    key2 <- mod_key0 key2_ [] 0 126
    hPutStr h2 (conv_txt4 (decrypt0 (conv_txt2 message char_set) key1 key2 (read (a !! 4)::Int)) char_set)
    hClose h0
    hClose h1
    hClose h2
  else putStrLn "Feature not implemented."

mod_key1 :: [Int] -> (IOArray Int Int) -> Int -> IO (IOArray Int Int)
mod_key1 [] inv_key c = return inv_key
mod_key1 (x:xs) inv_key c = do
  writeArray inv_key x c
  mod_key1 xs inv_key (c + 1)

mod_key0 :: (IOArray Int Int) -> [Int] -> Int -> Int -> IO [Int]
mod_key0 block lt c l = do
  if c == l then return lt
  else do
    a <- readArray block c
    mod_key0 block (lt ++ [a]) (c + 1) l

Description of the cipher

The algorithm is a symmetric block cipher with a 126 bit block size and approximately 999 bit keys. The current prototype can run in counter mode to produce a stream cipher or electronic code book mode, although due to the known weaknesses in this it is planned this will be replaced with cipher block chaining in the next version. The key is made up of two parts (1) and (2), each of which is converted internally to a sequence of numbers (seq (1) and seq (2)) with complexity of about 296 bits and 703 bits respectively. Key (1) is represented by a 64 character base 64 string within the character set:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 / (reference set)

For example:

KJW3nQCRagYr25po1yxku0tZXbGdhSDOfec7qM496zPsAFHwj/BIUELTlN 8Vivm

Each character in the set can only appear once so there are 64! possible states. Each character is converted to a number from 0 – 63 depending on its position in the reference set. Key (2) is represented by a 189 character base 64 string within the reference set. The following scheme is used to convert key (2) to a sequence of numbers between 0 – 125 internally.

in a string [x0, x1, xs] (Haskell grammar)

if x0 is not '/ ' then return 0 to 62 depending on the position of x0 in the reference set. (case 1)

else return 63 + (0 to 62) depending on the position of x1 in the reference set (case 2). Run the process again starting with x1 in case 1 or the first character in xs in case 2.

As each possible number can only appear once in seq (2) there are 126! possible states. Key (1) and (2) are used in the Sub Character and Swap Bit steps described below, respectively. These steps are performed in the order (Sub Character, Swap Bit) for block encryption and in reverse for block decryption. This sequence can be run an arbitrary number of times set by the user. In the examples shown here 10 rounds are used, which on a 1.6 GHz Intel Celeron CPU yields about 64 k bit / s plaintext processed in counter mode.

Sub Character step

In this step the input block is divided into 21 6 bit values. Each value n is substituted with the nth value in seq (1).

Swap Bit step

In this step the input block is treated as a sequence of 126 bits. The position of the nth bit is changed to the value of the nth entry in seq (2).

Implementation

The prototype is written in Haskell and compiles on GHC in Haskell platform 2013.2.0.0. It accepts as plaintext strings of characters in the set:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 /!\"£$%^&*()-_=+[]{};:'@#~\|,.<>.?\n\t`¬¥èéùìòÇØøÅå§ÄÖÑÜ¿öñüà€©®½¾

It returns a ciphertext within the reference set.

To use

For stream cipher mode encryption in the console run: [program file] s_encrypt [plaintext.txt] [key.txt] [ciphertext.txt] n (n = number of rounds)
For stream cipher mode decryption in the console run: [program file] s_decrypt [ciphertext.txt] [key.txt] [plaintext.txt] n (n = number of rounds)

Example ciphertext: aaDjBC28147bewcgNN9bbp2SVaB33nyEy1LoSDZ8g2ycOFTMoTobRkkMgzH6cX7djCtTxINXUkizph6XhljnyY0XfrgneIk9lNXk3HYGYLx3Bf9lrY4m4mj84w/pA9B4CCB7UGoSAwOODFewdrWskrmyVQxw4Hag1pwJv2vXzwzLqCzjrP7BmDfbN 2JN1o45zwwHXb3Moos1Vre9p0CWHaVL3Zrcw1eJlK9TE08OIDwfTVtrxEfbDqXtYeQsm3BuW7u2NC7lVs bfHxArHMw23w2toL8VJMeKmhpL7FCWr70lOsrGOMWsAC2xV4iKPnwpBvz0JaNVJX5h453u/b7sl18 U53UyVNy6JVpVaoJ SbOzftWbEqQWzxcvOJNXusfd9tyTJCWkmSX3htsSa6a1RWZgfManRw2ZBSA0ImYFBVCLkg2koQAHMcOHNEPsSQ/2isOpWQtYjOKb/ENpRqwiUP/Ha1H2niOVrU3R/r XnziIZYrWcOctoS//nbH4qJYM9HAkTVSwxHe lc1OygG/OYo /kPGqXXyPiv9Y218Ww1Qk1n7zpIlURLVHVIbZ36KMFF5sAH3dt6vb 3iRY8ecdRfIMOeAbkx8qAKrrhy/6TLGOfdUssN72qhGhp2Ym3ctpeyMoD9y oFzUpF LQog2Vj4kkj FKUzTZg2teYrEwU6CzfyX1FBXmTnxZ1x1GWCehLhksJTJxpTUVduW8EagWj8xiqKuf5mLiRIPCf2BhQjx7/FfJu3 BdbKOO/Bez5JsZGs/LGsJMvifib8mzJXWix1VniAoHzjTkQ1 c9QROpQU4N GWXTwQcmGCYtJrtnxyL0cZRZMjc96B93UhJxMUWWDiEsa/pmyyL15ZwKshazJEMUNUMt38FxemOjHYcVZ

Key: E1wXSsWrc25BJ98f7KtCudUmx3gFipYMVZ RyzbToqe6nDhHPON4l/GQAvIjkaL0m/c/N/SMK/W/XZPU/aFa/piYJ/m/ /r/v/1b/P/e3/2eQN/Y/k/4/sfr/nv/AHD/3ckw/g7Ono9/E/L/xyG/qjq/y/tA1g/R/F/O0/V/z/ZE/J/op4/DhC/Mt/jXV/d/B/b/H6/G/8/wW/C/5L2/fz/6R5/Txd/l/Q T/I/0/U8/i/uS/hu/7/KlB/9sI

Challenge ciphertext: GKjXqpq1iz/6OJ81WKNfOdcl5stnZlzI0NA88B/ASzvlB8mfzz66EckirfEvVMV6x30tdrTbe/oEqZdGUL/GCKIphqmW4x27oLtZtPUgVkuV75o WbfTDTYNWlSUmTTqHBhljMawa9a9IrXnpDg3ZKojlrSPPRNam6oyn44fU7oLaZMxJTDqM2BsfODeiGPfAtJw5Tl1onoCcrEyOYJiJnT53NSkFZmDQ52rO54j6UdBfeoBGZLFqurD1taqsHN3FZVZmjIY37ipvYDLlOXazkw44K1wV1j5tk8q9pP3iIP59SSNnK4r9gp09HAR0/ieNyZsKnDQujwZRXXsSlM/kFwEJdFgGsttNj7QJ77l9fuvkO3L3T0aHQfUwLQAtQXByAoc6/ lG/t7be9

Steven

Good post! I'll take a more closer look at it later today and tell you what I think.

Unfortunatly I don't speak much Haskell. Here's what I understand so far (my comments are in brackets):

1) Define the base 64 alphabet B to be:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 /

(I would choose better characters then ' ' and '/' since escaping them in implmentatin can be problamentic. Perhaps '+' and '='?)

2) Let k1 be a permutation of B.

3) Let k2 be 189 characters of B.

(Most block cyphers use 2^n bits with no rescrictions. This allows one block cipher to be substituted with another / chained, etc with ease. It also allows the key to be generated randomly with ease. This isn't so bad, but you do need to modify the software to accomidate. Imagine for example, if this cipher was used in a chip. You would need to redesign the circuit non-trivially to get it to work. One thing you can do to fix this is to specify how you generate k1 and k2 using one 2^n bits key. Keep in mind the complexity of the circuit.)

4) Let K1[x] be the position of k1[x] in B (starting with 'A' = 0).

5) If k2[x] is not '/', let K2[x] be the position of k2[x] in B. Else, {If k2[x+1] is not '/', let K2[x] be 63 + the position ...} (recursive). We remove all elements we "crunch togeather" in k1.

(This does something statiscically interesting. The odds of a number being > 62 are 1/62. The odds of a number being > then 124 are 1/62^2, etc.)

As each possible number can only appear once in seq (2) there are 126! possible states.

You'll need to explain this. I'm assuming that K2 = seq(2). You never placed any restrictions on the k2 string, other then it used the characters from B. What other restrictions are there? It is a permutation somehow? Also, k2 contains 189 characters. The number 126! does not seem to fit in here.

Perhaps I could have explained this a bit better. Another way to look at it is to start from the sequences of numbers that are input to each step and work back, but with a simplified example. If the block size was 16 bit and this was divided into 2 bit values (shown in decimal form) in the Sub Character step an iteration would look like this.

input data = 0 2 3 1 2 0 1 1, seq (1) = 1 3 0 2, output = 1 0 2 3 0 1 3 3

The meaning of seq (1) is: substitute 0 with 1, 1 with 3, 2 with 0 and 3 with 2. There are 4! possibilities for seq (1) as each value from 0 - 3 can only appear once for the step to preserve the information in the input. One could represent seq (1) by a string in the set "ABCD" by saying [A = 0, B = 1,...], giving key (1) = BDAC. In the Swap Bit step the input data is treated as a block of 16 bits, each of which has its position in the output determined by the value of the entry in seq (2) with the equivalent position.

input data = 0 1 0 0 1 0 1 1 0 0 0 1 1 1 1 1, seq (2) = 14 4 5 9 7 2 15 8 0 3 1 6 13 10 12 11, output = 0 0 0 0 1 0 1 1 1 0 1 1 1 1 0 1

As this changes the order of the bits there are 16! possibilities for seq (2). One could represent seq (2) by a string in the set "ABCDEFGH/". A letter on its own is read as [A = 0, B = 1,...] (scheme 1). A letter proceeded by '/' is read as the number given by putting the letter through scheme 1 plus 8. This gives key (2) = /GEF/BHC/H/AADBG/F/C/E/D.

Steven

That does give a little more information about the gut of the cipher. But I'm stilll having a little trouble understanding what you're saying about key (2). My interpratation is contradictory.

You mentioned that key (2) contains 189 characters of the base 64 alphabet.

Key (2) is represented by a 189 character base 64 string within the reference set.

Yet, you're also saying it's a permutation of the base 64 alphabet. And you're saying that somehow results in 126! combinations.

As each possible number can only appear once in seq (2) there are 126! possible states.

There is clearly something wrong about my interpretation.

If you can, can you give a short psuedocode example of reading in key (2) and converting it into seq (2)?

This look up table can be used to convert key (2) to seq (2).

ABCDEFGHIJ K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z  a  b  c  d  e 
0123456789 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30

f  g  h  i  j  k  l  m  n  o  p  q  r  s  t  u  v  w  x  y  z  0  1  2  3
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55

4  5  6  7  8  9  SPACE  /A /B /C /D /E /F /G /H /I /J /K /L /M /N /O /P
56 57 58 59 60 61 62     63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

/Q /R /S /T /U /V /W /X /Y /Z /a /b /c /d /e /f /g /h /i /j /k /l  /m  /n
79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101  102

/o    /p  /q   /r   /s   /t  /u  /v  /w  /x  /y /z  /0  /1  /2  /3  /4 
103  104  105  106  107  108 109 110 111 112 113 114 115 116 117 118 119

/5  /6  /7  /8  /9  SPACE
120 121 122 123 124 125

To come back to your point that most ciphers use an n bit key with no restrictions; I'm sure this is true. The logic of the cipher does lead to the potentially awkward requirement of generating two non - repeating sequences of numbers. I hadn't yet considered the issue of implementing the algorithm in hardware. The solution I came to for testing purposes was a Haskell program that takes a string in the reference set with no restrictions and outputs non - repeating strings for key (1) and (2).

In testing I used the site www.bitaddress.org to produce Bitcoin private keys, which I used for the input entropy. For several reasons this isn't suitable for production use, but for the statistical testing I've been doing I believe is sufficient. I'll try to post a C language version of the cipher and utilities at some point, to make some of this more accessible. Thanks for your input so far.

Steven

So cutting out the alphabet portion, is this correct (sorry, mind the mutation)?:

Let K1 be a permutation of the numbers 0 to 63 (using base 64 alphabet).
Let K2 be a permutation of the numbers 0 to 125 (using base 126 "alphabet").
Let n be the number of rounds.
Let p[i, b] the ith element in the b-bit array representing the plaintext.
Define sub(K1, p): p[i] = K1[p[i, 6]] for i = 0 to 21
Define swap(K2, p): swap(p[i, 1], p[K2[i], 1]) for i = 0 to 126
sub(K1, p), swap(K2, p) for i = 0 to n
Return c[i] = p[i, 6] for i = 0 to 21 as the cypthertext (using base 64 alphabet).

That appears to be right except (the mutation) where you've put i = 0 to 21 and i = 0 to 126 the upper bounds should be 20 and 125, to make 21 and 126 iterations respectively. I didn't state in the first post that in the examples the program was run in counter mode with an IV of 0, or equivalently "AAAAAAAAAAAAAAAAAAAAA". In this mode the block cipher is fed the sequence ["AAAAAAAAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAAAAAAB", "AAAAAAAAAAAAAAAAAAAAC",...] to encrypt, the result of which is xored with the plaintext.

Steven

What I ment by mutation was mutating variables (something that haskell tries to avoid), and yeah, my loops mirrored how a c loop would loop through an array, but it was inconsistant with the first 2 lines I wrote. Also, no recursion was intended on line 6. Anyways, on to initial impressions:

Clearly this is a substitution-permutation network. Here are a few of my first (nonformal) impressions:

1) The S-Box is just a permutation. That leads me to beleive that the cypher does not properly use Shannon's diffusion property, which means it's probably vunerable to a known-plaintext attack.

2) The idea of a P-Box is to "re-distribute" the bits such that the effects of the S-Box "Avalanche" nicely. Your P-Box method is a bit complicated, but from the looks of things there might exist plaintexts where we can wreck this distribution property with well chosen input, leading me to suspect that there are chosen-plaintext vunerabilities as well.

3) There is no key confusion, and it looks like there are classes of weak keys as well as related key attacks.

What makes this a bit more painfull is that the P and S-box's are based on the key. I don't think this makes it stronger, but there isn't as much research into it. I feel like this might turn into a bounding problem (bounding the posibilities for the keys) as more information is given.

An SP network is indeed the design principle I based this on. However, it isn't true that the S-Box is just a permutation and this can be shown using the scaled down example of the Sub Character step given earlier.

seq (1) = 1 3 0 2
input data = 0 2 3 1 2 0 1 1, output = 1 0 2 3 0 1 3 3

in binary: 00 10 11 01 10 00 01 01 01 00 10 11 00 01 11 11

That there are 7 1s in the input and 9 in the output proves this is not a permutation. This is a good job as if it were the two steps could be seen as a single permutation of the input. As for the P-Box (Swap Bit) my thinking is it's perhaps too simple. As a positive (to my mind) each bit in the input can be placed in any position in the output, meaning all permutations are possible and bias can only come from the entropy in the key. As a possible downside the same operation is used each time the P-Box is run unlike in more complex ciphers like AES, where a sequence of round keys derived from the main key are used. I'd be interested to hear your thoughts on this.

You may be right about there being issues with key confusion. I'm going to do a statistical test for the strict avalanche criterion at some point, which should shed some light on this.

Steven

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.