From ca0c3830ebad0eb61bb07344516a8e7fd272be03 Mon Sep 17 00:00:00 2001 From: Kei Hibino Date: Fri, 1 Apr 2016 19:25:04 +0900 Subject: [PATCH 1/4] Add implementation of CMAC. --- Crypto/MAC/CMAC.hs | 117 +++++++++++++++++++++++++++++++++++++++++++++ cryptonite.cabal | 1 + 2 files changed, 118 insertions(+) create mode 100644 Crypto/MAC/CMAC.hs diff --git a/Crypto/MAC/CMAC.hs b/Crypto/MAC/CMAC.hs new file mode 100644 index 0000000..a87d122 --- /dev/null +++ b/Crypto/MAC/CMAC.hs @@ -0,0 +1,117 @@ +-- | +-- Module : Crypto.MAC.CMAC +-- License : BSD-style +-- Maintainer : Kei Hibino +-- Stability : experimental +-- Portability : unknown +-- +-- provide the CMAC (Cipher based Message Authentification Code) base algorithm. +-- +-- +-- +module Crypto.MAC.CMAC + ( cmac + , subKeys + ) where + +import Data.Word +import Data.Bits (setBit, testBit, shiftL) +import Data.List (foldl') + +import Crypto.Cipher.Types +import Crypto.Internal.ByteArray (ByteArrayAccess, ByteArray, Bytes) +import qualified Crypto.Internal.ByteArray as B + + +-- | compute a MAC using the supplied cipher +cmac :: (ByteArrayAccess bin, ByteArray bout, BlockCipher cipher) + => cipher -- ^ key to compute CMAC with + -> bin -- ^ input message + -> bout -- ^ output tag +cmac k msg = + B.convert $ foldl' (\c m -> ecbEncrypt k $ bxor c m) zeroV ms + where + bytes = blockSize k + zeroV = B.replicate bytes 0 :: Bytes + (k1, k2) = subKeys k + ms = cmacChunks k k1 k2 $ B.convert msg + +cmacChunks :: (BlockCipher k, ByteArray ba) => k -> ba -> ba -> ba -> [ba] +cmacChunks k k1 k2 = rec' where + rec' msg + | B.null tl = if lack == 0 + then [bxor k1 hd] + else [bxor k2 $ hd `B.append` B.pack (0x80 : replicate (lack - 1) 0)] + | otherwise = hd : rec' tl + where + bytes = blockSize k + (hd, tl) = B.splitAt bytes msg + lack = bytes - B.length hd + +-- | make sub-keys used in CMAC +subKeys :: (BlockCipher k, ByteArray ba) + => k -- ^ key to compute CMAC with + -> (ba, ba) -- ^ sub-keys to compute CMAC +subKeys k = (k1, k2) where + ipt = cipherIPT k + k0 = ecbEncrypt k $ B.replicate (blockSize k) 0 + k1 = subKey ipt k0 + k2 = subKey ipt k1 + +-- polynomial multiply operation to culculate subkey +subKey :: (ByteArray ba) => [Word8] -> ba -> ba +subKey ipt ws = case B.unpack ws of + [] -> B.empty + w:_ | testBit w 7 -> B.pack ipt `bxor` shiftL1 ws + | otherwise -> shiftL1 ws + +shiftL1 :: (ByteArray ba) => ba -> ba +shiftL1 = B.pack . shiftL1W . B.unpack + +shiftL1W :: [Word8] -> [Word8] +shiftL1W [] = [] +shiftL1W ws@(_:ns) = rec' $ zip ws (ns ++ [0]) where + rec' [] = [] + rec' ((x,y):ps) = w : rec' ps + where + w | testBit y 7 = setBit sl1 0 + | otherwise = sl1 + where sl1 = shiftL x 1 + +bxor :: ByteArray ba => ba -> ba -> ba +bxor = B.xor + + +----- + + +cipherIPT :: BlockCipher k => k -> [Word8] +cipherIPT = expandIPT . blockSize where + +data IPolynomial + = Q Int Int Int +--- | T Int + +iPolynomial :: Int -> Maybe IPolynomial +iPolynomial = d where + d 64 = Just $ Q 4 3 1 + d 128 = Just $ Q 7 2 1 + d _ = Nothing + +-- Expand a tail bit pattern of irreducible binary polynomial +expandIPT :: Int -> [Word8] +expandIPT bytes = expandIPT' bytes ipt where + ipt = maybe (error $ "Irreducible binary polynomial not defined against " ++ show nb ++ " bit") id + $ iPolynomial nb + nb = bytes * 8 + +-- Expand a tail bit pattern of irreducible binary polynomial +expandIPT' :: Int -- ^ width in byte + -> IPolynomial -- ^ irreducible binary polynomial definition + -> [Word8] -- ^ result bit pattern +expandIPT' bytes (Q x y z) = + reverse . setB x . setB y . setB z . setB 0 $ replicate bytes 0 + where + setB i ws = hd ++ setBit (head tl) r : tail tl where + (q, r) = i `quotRem` 8 + (hd, tl) = splitAt q ws diff --git a/cryptonite.cabal b/cryptonite.cabal index 35afe1a..34727bf 100644 --- a/cryptonite.cabal +++ b/cryptonite.cabal @@ -100,6 +100,7 @@ Library Crypto.Data.AFIS Crypto.Data.Padding Crypto.Error + Crypto.MAC.CMAC Crypto.MAC.Poly1305 Crypto.MAC.HMAC Crypto.Number.Basic From b704f2c02ad4d66395ec24b5eedc2d32bc83c6fd Mon Sep 17 00:00:00 2001 From: Kei Hibino Date: Fri, 1 Apr 2016 19:26:16 +0900 Subject: [PATCH 2/4] Add test-suite of CMAC. --- cryptonite.cabal | 1 + tests/KAT_CMAC.hs | 206 ++++++++++++++++++++++++++++++++++++++++++++++ tests/Tests.hs | 2 + 3 files changed, 209 insertions(+) create mode 100644 tests/KAT_CMAC.hs diff --git a/cryptonite.cabal b/cryptonite.cabal index 34727bf..d3da4ed 100644 --- a/cryptonite.cabal +++ b/cryptonite.cabal @@ -304,6 +304,7 @@ Test-Suite test-cryptonite KAT_Curve25519 KAT_DES KAT_Ed25519 + KAT_CMAC KAT_HMAC KAT_PBKDF2 KAT_PubKey.DSA diff --git a/tests/KAT_CMAC.hs b/tests/KAT_CMAC.hs new file mode 100644 index 0000000..4dff007 --- /dev/null +++ b/tests/KAT_CMAC.hs @@ -0,0 +1,206 @@ + +module KAT_CMAC (tests) where + +import qualified Crypto.MAC.CMAC as CMAC +import Crypto.Cipher.Types (Cipher, cipherInit, BlockCipher, ecbEncrypt, blockSize) +import Crypto.Error (eitherCryptoError) +import Crypto.Cipher.AES (AES128, AES192, AES256) +import Crypto.Cipher.TripleDES (DES_EDE3, DES_EDE2) + +import Imports + +import Data.Char (digitToInt) +import qualified Data.ByteString as BS + + +hxs :: String -> ByteString +hxs = BS.pack . rec' where + dtoW8 = fromIntegral . digitToInt + rec' (' ':xs) = rec' xs + rec' (x:y:xs) = dtoW8 x * 16 + dtoW8 y : rec' xs + rec' [_] = error "hxs: invalid hex pattern." + rec' [] = [] + +unsafeCipher :: Cipher k => ByteString -> k +unsafeCipher = either (error . show) id . eitherCryptoError . cipherInit + +ecb0 :: BlockCipher k => k -> ByteString +ecb0 k = ecbEncrypt k $ BS.replicate (blockSize k) 0 + +{- Test vectors from NIST data-sheet + (AES128-CMAC, AES192-CMAC, AES256-CMAC, Three Key TDEA, Two Key TDEA) + http://csrc.nist.gov/publications/nistpubs/800-38B/Updated_CMAC_Examples.pdf + The data of AES128-CMAC is same as them in RFC4493. + -} + +msg512 :: ByteString +msg512 = + hxs $ + "6bc1bee2 2e409f96 e93d7e11 7393172a" ++ + "ae2d8a57 1e03ac9c 9eb76fac 45af8e51" ++ + "30c81c46 a35ce411 e5fbc119 1a0a52ef" ++ + "f69f2445 df4f9b17 ad2b417b e66c3710" + +msg320 :: ByteString +msg320 = BS.take 40 msg512 + +msg256 :: ByteString +msg256 = BS.take 32 msg512 + +msg160 :: ByteString +msg160 = BS.take 20 msg512 + +msg128 :: ByteString +msg128 = BS.take 16 msg512 + +msg64 :: ByteString +msg64 = BS.take 8 msg512 + +msg0 :: ByteString +msg0 = BS.empty + +gAES128 :: TestTree +gAES128 = + igroup "aes128" + [ ecb0 aes128key @?= hxs "7df76b0c 1ab899b3 3e42f047 b91b546f" + , aes128k1 @?= hxs "fbeed618 35713366 7c85e08f 7236a8de" + , aes128k2 @?= hxs "f7ddac30 6ae266cc f90bc11e e46d513b" + + , CMAC.cmac aes128key msg0 + @?= hxs "bb1d6929 e9593728 7fa37d12 9b756746" + , CMAC.cmac aes128key msg128 + @?= hxs "070a16b4 6b4d4144 f79bdd9d d04a287c" + , CMAC.cmac aes128key msg320 + @?= hxs "dfa66747 de9ae630 30ca3261 1497c827" + , CMAC.cmac aes128key msg512 + @?= hxs "51f0bebf 7e3b9d92 fc497417 79363cfe" + ] + where + aes128key :: AES128 + aes128key = + unsafeCipher $ hxs + "2b7e1516 28aed2a6 abf71588 09cf4f3c" + + aes128k1, aes128k2 :: ByteString + (aes128k1, aes128k2) = CMAC.subKeys aes128key + + +gAES192 :: TestTree +gAES192 = + igroup "aes192" + [ ecb0 aes192key @?= hxs "22452d8e 49a8a593 9f7321ce ea6d514b" + , aes192k1 @?= hxs "448a5b1c 93514b27 3ee6439d d4daa296" + , aes192k2 @?= hxs "8914b639 26a2964e 7dcc873b a9b5452c" + + , CMAC.cmac aes192key msg0 + @?= hxs "d17ddf46 adaacde5 31cac483 de7a9367" + , CMAC.cmac aes192key msg128 + @?= hxs "9e99a7bf 31e71090 0662f65e 617c5184" + , CMAC.cmac aes192key msg320 + @?= hxs "8a1de5be 2eb31aad 089a82e6 ee908b0e" + , CMAC.cmac aes192key msg512 + @?= hxs "a1d5df0e ed790f79 4d775896 59f39a11" + ] + where + aes192key :: AES192 + aes192key = + unsafeCipher . hxs $ + "8e73b0f7 da0e6452 c810f32b 809079e5" ++ + "62f8ead2 522c6b7b" + + aes192k1, aes192k2 :: ByteString + (aes192k1, aes192k2) = CMAC.subKeys aes192key + +gAES256 :: TestTree +gAES256 = + igroup "aes256" + [ ecb0 aes256key @?= hxs "e568f681 94cf76d6 174d4cc0 4310a854" + , aes256k1 @?= hxs "cad1ed03 299eedac 2e9a9980 8621502f" + , aes256k2 @?= hxs "95a3da06 533ddb58 5d353301 0c42a0d9" + + , CMAC.cmac aes256key msg0 + @?= hxs "028962f6 1b7bf89e fc6b551f 4667d983" + , CMAC.cmac aes256key msg128 + @?= hxs "28a7023f 452e8f82 bd4bf28d 8c37c35c" + , CMAC.cmac aes256key msg320 + @?= hxs "aaf3d8f1 de5640c2 32f5b169 b9c911e6" + , CMAC.cmac aes256key msg512 + @?= hxs "e1992190 549f6ed5 696a2c05 6c315410" + ] + where + aes256key :: AES256 + aes256key = + unsafeCipher . hxs $ + "603deb10 15ca71be 2b73aef0 857d7781" ++ + "1f352c07 3b6108d7 2d9810a3 0914dff4" + + aes256k1, aes256k2 :: ByteString + (aes256k1, aes256k2) = CMAC.subKeys aes256key + +gTDEA3 :: TestTree +gTDEA3 = + igroup "Three Key TDEA" + [ ecb0 tdea3key @?= hxs "c8cc74e9 8a7329a2" + , tdea3k1 @?= hxs "9198e9d3 14e6535f" + , tdea3k2 @?= hxs "2331d3a6 29cca6a5" + + , CMAC.cmac tdea3key msg0 + @?= hxs "b7a688e1 22ffaf95" + , CMAC.cmac tdea3key msg64 + @?= hxs "8e8f2931 36283797" + , CMAC.cmac tdea3key msg160 + @?= hxs "743ddbe0 ce2dc2ed" + , CMAC.cmac tdea3key msg256 + @?= hxs "33e6b109 2400eae5" + ] + where + tdea3key :: DES_EDE3 + tdea3key = + unsafeCipher . hxs $ + "8aa83bf8 cbda1062" ++ + "0bc1bf19 fbb6cd58" ++ + "bc313d4a 371ca8b5" + + tdea3k1, tdea3k2 :: ByteString + (tdea3k1, tdea3k2) = CMAC.subKeys tdea3key + +gTDEA2 :: TestTree +gTDEA2 = + igroup "Two Key TDEA" + [ ecb0 tdea2key @?= hxs "c7679b9f 6b8d7d7a" + , tdea2k1 @?= hxs "8ecf373e d71afaef" + , tdea2k2 @?= hxs "1d9e6e7d ae35f5c5" + + , CMAC.cmac tdea2key msg0 + @?= hxs "bd2ebf9a 3ba00361" + , CMAC.cmac tdea2key msg64 + @?= hxs "4ff2ab81 3c53ce83" + , CMAC.cmac tdea2key msg160 + @?= hxs "62dd1b47 1902bd4e" + , CMAC.cmac tdea2key msg256 + @?= hxs "31b1e431 dabc4eb8" + ] + where + tdea2key :: DES_EDE2 + tdea2key = + unsafeCipher . hxs $ + "4cf15134 a2850dd5" ++ + "8a3d10ba 80570d38" + + tdea2k1, tdea2k2 :: ByteString + (tdea2k1, tdea2k2) = CMAC.subKeys tdea2key + +igroup :: TestName -> [Assertion] -> TestTree +igroup nm = testGroup nm . zipWith (flip ($)) [1..] . map icase + where + icase c i = testCase (show (i :: Int)) c + +nistVectors :: TestTree +nistVectors = + testGroup "KAT - NIST test vectors" + [ gAES128, gAES192, gAES256, gTDEA3, gTDEA2 ] + +tests :: TestTree +tests = + testGroup "CMAC" + [ nistVectors ] diff --git a/tests/Tests.hs b/tests/Tests.hs index 640eeb3..777cef3 100644 --- a/tests/Tests.hs +++ b/tests/Tests.hs @@ -10,6 +10,7 @@ import qualified Poly1305 import qualified Salsa import qualified ChaCha import qualified ChaChaPoly1305 +import qualified KAT_CMAC import qualified KAT_HMAC import qualified KAT_HKDF import qualified KAT_PBKDF2 @@ -35,6 +36,7 @@ tests = testGroup "cryptonite" , Padding.tests , testGroup "MAC" [ Poly1305.tests + , KAT_CMAC.tests , KAT_HMAC.tests ] , KAT_Curve25519.tests From 4442744b1de21c58c5195eb04ed87e18f1356071 Mon Sep 17 00:00:00 2001 From: Kei Hibino Date: Wed, 6 Apr 2016 09:41:50 +0900 Subject: [PATCH 3/4] Add the smart constructor of CMAC type. --- Crypto/MAC/CMAC.hs | 18 +++++++++++++----- tests/KAT_CMAC.hs | 44 ++++++++++++++++++++++++-------------------- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/Crypto/MAC/CMAC.hs b/Crypto/MAC/CMAC.hs index a87d122..23ee66e 100644 --- a/Crypto/MAC/CMAC.hs +++ b/Crypto/MAC/CMAC.hs @@ -9,8 +9,10 @@ -- -- -- +{-# LANGUAGE GeneralizedNewtypeDeriving #-} module Crypto.MAC.CMAC ( cmac + , CMAC(..) , subKeys ) where @@ -23,13 +25,19 @@ import Crypto.Internal.ByteArray (ByteArrayAccess, ByteArray, Bytes) import qualified Crypto.Internal.ByteArray as B +newtype CMAC a = CMAC { cmacGetBytes :: Bytes } + deriving ByteArrayAccess + +instance Eq (CMAC a) where + CMAC b1 == CMAC b2 = B.constEq b1 b2 + -- | compute a MAC using the supplied cipher -cmac :: (ByteArrayAccess bin, ByteArray bout, BlockCipher cipher) - => cipher -- ^ key to compute CMAC with - -> bin -- ^ input message - -> bout -- ^ output tag +cmac :: (ByteArrayAccess bin, BlockCipher cipher) + => cipher -- ^ key to compute CMAC with + -> bin -- ^ input message + -> CMAC cipher -- ^ output tag cmac k msg = - B.convert $ foldl' (\c m -> ecbEncrypt k $ bxor c m) zeroV ms + CMAC $ foldl' (\c m -> ecbEncrypt k $ bxor c m) zeroV ms where bytes = blockSize k zeroV = B.replicate bytes 0 :: Bytes diff --git a/tests/KAT_CMAC.hs b/tests/KAT_CMAC.hs index 4dff007..5f2addd 100644 --- a/tests/KAT_CMAC.hs +++ b/tests/KAT_CMAC.hs @@ -11,6 +11,7 @@ import Imports import Data.Char (digitToInt) import qualified Data.ByteString as BS +import qualified Data.ByteArray as B hxs :: String -> ByteString @@ -59,6 +60,9 @@ msg64 = BS.take 8 msg512 msg0 :: ByteString msg0 = BS.empty +bsCMAC :: BlockCipher k => k -> ByteString -> ByteString +bsCMAC k = B.convert . CMAC.cmacGetBytes . CMAC.cmac k + gAES128 :: TestTree gAES128 = igroup "aes128" @@ -66,13 +70,13 @@ gAES128 = , aes128k1 @?= hxs "fbeed618 35713366 7c85e08f 7236a8de" , aes128k2 @?= hxs "f7ddac30 6ae266cc f90bc11e e46d513b" - , CMAC.cmac aes128key msg0 + , bsCMAC aes128key msg0 @?= hxs "bb1d6929 e9593728 7fa37d12 9b756746" - , CMAC.cmac aes128key msg128 + , bsCMAC aes128key msg128 @?= hxs "070a16b4 6b4d4144 f79bdd9d d04a287c" - , CMAC.cmac aes128key msg320 + , bsCMAC aes128key msg320 @?= hxs "dfa66747 de9ae630 30ca3261 1497c827" - , CMAC.cmac aes128key msg512 + , bsCMAC aes128key msg512 @?= hxs "51f0bebf 7e3b9d92 fc497417 79363cfe" ] where @@ -92,13 +96,13 @@ gAES192 = , aes192k1 @?= hxs "448a5b1c 93514b27 3ee6439d d4daa296" , aes192k2 @?= hxs "8914b639 26a2964e 7dcc873b a9b5452c" - , CMAC.cmac aes192key msg0 + , bsCMAC aes192key msg0 @?= hxs "d17ddf46 adaacde5 31cac483 de7a9367" - , CMAC.cmac aes192key msg128 + , bsCMAC aes192key msg128 @?= hxs "9e99a7bf 31e71090 0662f65e 617c5184" - , CMAC.cmac aes192key msg320 + , bsCMAC aes192key msg320 @?= hxs "8a1de5be 2eb31aad 089a82e6 ee908b0e" - , CMAC.cmac aes192key msg512 + , bsCMAC aes192key msg512 @?= hxs "a1d5df0e ed790f79 4d775896 59f39a11" ] where @@ -118,13 +122,13 @@ gAES256 = , aes256k1 @?= hxs "cad1ed03 299eedac 2e9a9980 8621502f" , aes256k2 @?= hxs "95a3da06 533ddb58 5d353301 0c42a0d9" - , CMAC.cmac aes256key msg0 + , bsCMAC aes256key msg0 @?= hxs "028962f6 1b7bf89e fc6b551f 4667d983" - , CMAC.cmac aes256key msg128 + , bsCMAC aes256key msg128 @?= hxs "28a7023f 452e8f82 bd4bf28d 8c37c35c" - , CMAC.cmac aes256key msg320 + , bsCMAC aes256key msg320 @?= hxs "aaf3d8f1 de5640c2 32f5b169 b9c911e6" - , CMAC.cmac aes256key msg512 + , bsCMAC aes256key msg512 @?= hxs "e1992190 549f6ed5 696a2c05 6c315410" ] where @@ -144,13 +148,13 @@ gTDEA3 = , tdea3k1 @?= hxs "9198e9d3 14e6535f" , tdea3k2 @?= hxs "2331d3a6 29cca6a5" - , CMAC.cmac tdea3key msg0 + , bsCMAC tdea3key msg0 @?= hxs "b7a688e1 22ffaf95" - , CMAC.cmac tdea3key msg64 + , bsCMAC tdea3key msg64 @?= hxs "8e8f2931 36283797" - , CMAC.cmac tdea3key msg160 + , bsCMAC tdea3key msg160 @?= hxs "743ddbe0 ce2dc2ed" - , CMAC.cmac tdea3key msg256 + , bsCMAC tdea3key msg256 @?= hxs "33e6b109 2400eae5" ] where @@ -171,13 +175,13 @@ gTDEA2 = , tdea2k1 @?= hxs "8ecf373e d71afaef" , tdea2k2 @?= hxs "1d9e6e7d ae35f5c5" - , CMAC.cmac tdea2key msg0 + , bsCMAC tdea2key msg0 @?= hxs "bd2ebf9a 3ba00361" - , CMAC.cmac tdea2key msg64 + , bsCMAC tdea2key msg64 @?= hxs "4ff2ab81 3c53ce83" - , CMAC.cmac tdea2key msg160 + , bsCMAC tdea2key msg160 @?= hxs "62dd1b47 1902bd4e" - , CMAC.cmac tdea2key msg256 + , bsCMAC tdea2key msg256 @?= hxs "31b1e431 dabc4eb8" ] where From 327d75c2d47febe97fb08822dbe33b3d3c209f28 Mon Sep 17 00:00:00 2001 From: Kei Hibino Date: Wed, 6 Apr 2016 11:59:26 +0900 Subject: [PATCH 4/4] Add comments about irreducible binary polynomial. --- Crypto/MAC/CMAC.hs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Crypto/MAC/CMAC.hs b/Crypto/MAC/CMAC.hs index 23ee66e..86bf464 100644 --- a/Crypto/MAC/CMAC.hs +++ b/Crypto/MAC/CMAC.hs @@ -96,6 +96,13 @@ bxor = B.xor cipherIPT :: BlockCipher k => k -> [Word8] cipherIPT = expandIPT . blockSize where +-- Data type which represents the smallest irreducibule binary polynomial +-- against specified degree. +-- +-- Maximum degree bit and degree 0 bit are omitted. +-- For example, The value /Q 7 2 1/ corresponds to the degree /128/. +-- It represents that the smallest irreducible binary polynomial of degree 128 +-- is x^128 + x^7 + x^2 + x^1 + 1. data IPolynomial = Q Int Int Int --- | T Int