这个Haskell AES密钥扩展有什么问题?

时间:2017-05-12 18:48:29

标签: haskell aes rijndael

按照Jeff Moser's popular tutorial中描述密钥扩展的步骤,我已经为密钥扩展编写了此代码。这是整个文件(也计算S-Box),所以人们可以编译并尝试它。

{-# LANGUAGE NoMonomorphismRestriction #-}

import Control.Applicative (liftA2)
import Data.Bits (xor, shiftL, shiftR, (.|.), (.&.))
import Data.List (transpose, sortBy)
import Data.Ord (comparing)
import Data.Word (Word8)
import Numeric (showHex)

keys = f 16 $ f 8 $ f 4 $ f 2 $ f 1 key
 where
  f w n = xpndC . xpndB . xpndA $ xpndD w n

xpndC   :: [[Word8]] -> [[Word8]]
xpndC ws = transpose [head ws, b, zipWith xor b c, last ws]
 where
  (b,c) = (ws !! 1, ws !! 2)

xpndB   :: [[Word8]] -> [[Word8]]
xpndB ws = a : zipWith xor a b : drop 2 ws
 where
  (a,b) = (head ws, ws !! 1)

xpndA   :: [[Word8]] -> [[Word8]]
xpndA ws = zipWith xor a d : tail ws
 where
  (a,d) = (head ws, last ws)

xpndD rc ws = take 3 tW ++ [w']
 where
  w' = zipWith xor (map sub w) [rc, 0, 0, 0]
  tW = transpose ws
  w  = take 4 $ tail $ cycle $ last tW

--------------------------------------------------------------
sub w = get sbox (fromIntegral lo) $ fromIntegral hi
 where
  (hi, lo) = nibs w

get wss x y = (wss !! y) !! x

print' = print . w128 . concat . transpose
 where
  w128 = concatMap (f . (`showHex` ""))
  f w  = (length w < 2) ? (' ':'0':w, ' ':w)

grid _ [] = []
grid n xs = take n xs : grid n (drop n xs)

nibs w    = (shiftR (w .&. 0xF0) 4, w .&. 0x0F)
(⊕)       = xor
p ? (a,b) = if p then a else b; infix 2 ?

---------------------------------------------------
sbox :: [[Word8]]
sbox = grid 16 $ map snd $ sortBy (comparing fst) $ sbx 1 1 []

sbx :: Word8 -> Word8 -> [(Word8, Word8)] -> [(Word8, Word8)]
sbx p q ws
  | length ws == 255 = (0, 0x63) : ws
  | otherwise = sbx p' r $ (p', xf ⊕ 0x63) : ws
 where
  p' = p  ⊕  shiftL p  1 ⊕  ((p .&. 0x80 /= 0) ? (0x1B, 0))
  q1 = foldl (liftA2 (.) xor shiftL) q [1, 2, 4]
  r  = q1 ⊕  ((q1 .&. 0x80 /= 0) ? (0x09, 0))
  xf = r  ⊕  rotl8 r 1 ⊕  rotl8 r 2 ⊕  rotl8 r 3 ⊕  rotl8 r 4

rotl8 w n = (w `shiftL` n) .|. (w `shiftR` (8 - n))

key = [[0,0,0,0],
       [0,0,0,0],
       [0,0,0,0],
       [0,0,0,0]] :: [[Word8]]

当我针对全零测试密钥测试此代码时,它匹配已发布的期望直到第四次迭代:ee 06 da 7b 87 6a 15 81 75 9e 42 b2 7e 91 ee 2b

但是当我尝试下一次迭代时:keys = f 16 $ f 8 $ f 4 $ f 2 $ f 1, 结果的最后32位是错误的:7f 2e 2b 88 f8 44 3e 09 8d da 7c bb 91 28 f1 f3

当我使用所有0xFF作为初始密钥时,会发生相同的行为 - 最后32位错误。在后续迭代中,所有位都是错误的。

如果我使用测试向量00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f,那么事情会更快出错 - 我在第二次迭代时开始得到错误的位。

这里发生了什么?我注意到Moser先生在 2b:4 中用{em>上一轮密钥的第一列写了xor - 但是没有前一轮的初始关键,所以这让我很困惑。这是我做错了吗?

供参考,here are the test vectors.

1 个答案:

答案 0 :(得分:3)

你错过了一步。

xpndC ws = transpose [head ws, b, zipWith xor b c, last ws]

第四列应该是前一个第四列的xor(你在第一遍中扔掉的)和新的第三列。

只有在第五次迭代时,xor x x = 0以某种方式促成了这个错误才会引起注意。

轻微的风格评论

固定结构上的模式匹配不如(!!)笨拙。

xpndC :: [[Word8]] -> [[Word8]]
xpndC [a,b,c,d] = [a, b, zipWith xor b c, d]

另请注意,步骤2b43实际上是一次扫描。粗略地说,它看起来像这样(从你的上一个链接借用名称schedule_core):

new = tail $ scanl (zipWith xor) (schedule_core (last old)) old

编辑:修复

解决方案基本上是扔掉最后一列。作为快速修复,您可以通过这种方式将其注入另外的通道中:

keys = f 16 $ f 8 $ f 4 $ f 2 $ f 1 key
 where
  f w n = xpndE (transpose n) . xpndC . xpndB . xpndA $ xpndD w n

xpndE n [a,b,c,_] = transpose [a,b,c,zipWith xor c (last n)]

xpndC = (...) {- remove transpose here -}

一旦您意识到列表非常小,xpnd*函数可能有点太精细了。如果你想保留它,我也会考虑transpose

keys = transpose $ f 16 $ f 8 $ f 4 $ f 2 $ f 1 $ transpose key
  where
    f rc [a, b, c, d] =
      let e = schedule rc d
          a' = zipWith xor a e
          b' = zipWith xor b a'
          c' = zipWith xor c b'
          d' = zipWith xor c c'
      in [a', b', c', d']  -- Here is where one may recognize `scanl` or a fold.

对于schedule,它是取最后一列(上面d,下面是last tW)并对其进行加扰的函数(上面ew'下面)。您可以从xpndD

的定义中提取它
xpndD rc ws = take 3 tW ++ [w']
 where
  w' = zipWith xor (map sub w) [rc, 0, 0, 0]
  tW = transpose ws
  w  = take 4 $ tail $ cycle $ last tW

我们得到(模数纯粹的化妆品重写take 4 $ tail $ cycle d = tail d ++ [head d]):

schedule rc d = zipWith xor (map sub $ tail d ++ [head d]) [rc, 0, 0, 0]