File: Primitive.hs

package info (click to toggle)
haskell-crypton 1.0.4-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,548 kB
  • sloc: haskell: 26,764; ansic: 22,294; makefile: 6
file content (284 lines) | stat: -rw-r--r-- 9,759 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
-- Rewritten by Vincent Hanquez (c) 2015
--              Lars Petersen (c) 2018
--
-- Original code:
--      Crypto.Cipher.Blowfish.Primitive, copyright (c) 2012 Stijn van Drongelen
--      based on: BlowfishAux.hs (C) 2002 HardCore SoftWare, Doug Hoyte
--           (as found in Crypto-4.2.4)
{-# LANGUAGE BangPatterns #-}

-- |
-- Module      : Crypto.Cipher.Blowfish.Primitive
-- License     : BSD-style
-- Stability   : experimental
-- Portability : Good
module Crypto.Cipher.Blowfish.Primitive (
    Context,
    initBlowfish,
    encrypt,
    decrypt,
    KeySchedule,
    createKeySchedule,
    freezeKeySchedule,
    expandKey,
    expandKeyWithSalt,
    cipherBlockMutable,
) where

import Control.Monad (when)
import Data.Bits
import Data.Memory.Endian
import Data.Word

import Crypto.Cipher.Blowfish.Box
import Crypto.Error
import Crypto.Internal.ByteArray (ByteArray, ByteArrayAccess)
import qualified Crypto.Internal.ByteArray as B
import Crypto.Internal.Compat
import Crypto.Internal.Imports
import Crypto.Internal.WordArray

newtype Context = Context Array32

instance NFData Context where
    rnf a = a `seq` ()

-- | Initialize a new Blowfish context from a key.
--
-- key needs to be between 0 and 448 bits.
initBlowfish :: ByteArrayAccess key => key -> CryptoFailable Context
initBlowfish key
    | B.length key > (448 `div` 8) = CryptoFailed CryptoError_KeySizeInvalid
    | otherwise = CryptoPassed $ unsafeDoIO $ do
        ks <- createKeySchedule
        expandKey ks key
        freezeKeySchedule ks

-- | Get an immutable Blowfish context by freezing a mutable key schedule.
freezeKeySchedule :: KeySchedule -> IO Context
freezeKeySchedule (KeySchedule ma) = Context `fmap` mutableArray32Freeze ma

expandKey :: ByteArrayAccess key => KeySchedule -> key -> IO ()
expandKey ks@(KeySchedule ma) key = do
    when (B.length key > 0) $ iterKeyStream key 0 0 $ \i l r a0 a1 cont -> do
        mutableArrayWriteXor32 ma i l
        mutableArrayWriteXor32 ma (i + 1) r
        when (i + 2 < 18) (cont a0 a1)
    loop 0 0 0
  where
    loop i l r = do
        n <- cipherBlockMutable ks (fromIntegral l `shiftL` 32 .|. fromIntegral r)
        let nl = fromIntegral (n `shiftR` 32)
            nr = fromIntegral (n .&. 0xffffffff)
        mutableArrayWrite32 ma i nl
        mutableArrayWrite32 ma (i + 1) nr
        when (i < 18 + 1024) (loop (i + 2) nl nr)

expandKeyWithSalt
    :: (ByteArrayAccess key, ByteArrayAccess salt)
    => KeySchedule
    -> key
    -> salt
    -> IO ()
expandKeyWithSalt ks key salt
    | B.length salt == 16 =
        expandKeyWithSalt128
            ks
            key
            (fromBE $ B.toW64BE salt 0)
            (fromBE $ B.toW64BE salt 8)
    | otherwise = expandKeyWithSaltAny ks key salt

expandKeyWithSaltAny
    :: (ByteArrayAccess key, ByteArrayAccess salt)
    => KeySchedule
    -- ^ The key schedule
    -> key
    -- ^ The key
    -> salt
    -- ^ The salt
    -> IO ()
expandKeyWithSaltAny ks@(KeySchedule ma) key salt = do
    when (B.length key > 0) $ iterKeyStream key 0 0 $ \i l r a0 a1 cont -> do
        mutableArrayWriteXor32 ma i l
        mutableArrayWriteXor32 ma (i + 1) r
        when (i + 2 < 18) (cont a0 a1)
    -- Go through the entire key schedule overwriting the P-Array and S-Boxes
    when (B.length salt > 0) $ iterKeyStream salt 0 0 $ \i l r a0 a1 cont -> do
        let l' = xor l a0
        let r' = xor r a1
        n <- cipherBlockMutable ks (fromIntegral l' `shiftL` 32 .|. fromIntegral r')
        let nl = fromIntegral (n `shiftR` 32)
            nr = fromIntegral (n .&. 0xffffffff)
        mutableArrayWrite32 ma i nl
        mutableArrayWrite32 ma (i + 1) nr
        when (i + 2 < 18 + 1024) (cont nl nr)

expandKeyWithSalt128
    :: ByteArrayAccess ba
    => KeySchedule
    -- ^ The key schedule
    -> ba
    -- ^ The key
    -> Word64
    -- ^ First word of the salt
    -> Word64
    -- ^ Second word of the salt
    -> IO ()
expandKeyWithSalt128 ks@(KeySchedule ma) key salt1 salt2 = do
    when (B.length key > 0) $ iterKeyStream key 0 0 $ \i l r a0 a1 cont -> do
        mutableArrayWriteXor32 ma i l
        mutableArrayWriteXor32 ma (i + 1) r
        when (i + 2 < 18) (cont a0 a1)
    -- Go through the entire key schedule overwriting the P-Array and S-Boxes
    loop 0 salt1 salt1 salt2
  where
    loop i input slt1 slt2
        | i == 1042 = return ()
        | otherwise = do
            n <- cipherBlockMutable ks input
            let nl = fromIntegral (n `shiftR` 32)
                nr = fromIntegral (n .&. 0xffffffff)
            mutableArrayWrite32 ma i nl
            mutableArrayWrite32 ma (i + 1) nr
            loop (i + 2) (n `xor` slt2) slt2 slt1

-- | Encrypt blocks
--
-- Input need to be a multiple of 8 bytes
encrypt :: ByteArray ba => Context -> ba -> ba
encrypt ctx ba
    | B.length ba == 0 = B.empty
    | B.length ba `mod` 8 /= 0 = error "invalid data length"
    | otherwise = B.mapAsWord64 (cipherBlock ctx False) ba

-- | Decrypt blocks
--
-- Input need to be a multiple of 8 bytes
decrypt :: ByteArray ba => Context -> ba -> ba
decrypt ctx ba
    | B.length ba == 0 = B.empty
    | B.length ba `mod` 8 /= 0 = error "invalid data length"
    | otherwise = B.mapAsWord64 (cipherBlock ctx True) ba

-- | Encrypt or decrypt a single block of 64 bits.
--
-- The inverse argument decides whether to encrypt or decrypt.
cipherBlock :: Context -> Bool -> Word64 -> Word64
cipherBlock (Context ar) inverse input = doRound input 0
  where
    -- \| Transform the input over 16 rounds
    doRound :: Word64 -> Int -> Word64
    doRound !i roundIndex
        | roundIndex == 16 =
            let final = (fromIntegral (p 16) `shiftL` 32) .|. fromIntegral (p 17)
             in rotateL (i `xor` final) 32
        | otherwise =
            let newr = fromIntegral (i `shiftR` 32) `xor` p roundIndex
                newi = ((i `shiftL` 32) `xor` f newr) .|. fromIntegral newr
             in doRound newi (roundIndex + 1)

    -- \| The Blowfish Feistel function F
    f :: Word32 -> Word64
    f t =
        let a = s0 (0xff .&. (t `shiftR` 24))
            b = s1 (0xff .&. (t `shiftR` 16))
            c = s2 (0xff .&. (t `shiftR` 8))
            d = s3 (0xff .&. t)
         in fromIntegral (((a + b) `xor` c) + d) `shiftL` 32

    -- \| S-Box arrays, each containing 256 32-bit words
    --   The first 18 words contain the P-Array of subkeys
    s0, s1, s2, s3 :: Word32 -> Word32
    s0 i = arrayRead32 ar (fromIntegral i + 18)
    s1 i = arrayRead32 ar (fromIntegral i + 274)
    s2 i = arrayRead32 ar (fromIntegral i + 530)
    s3 i = arrayRead32 ar (fromIntegral i + 786)
    p :: Int -> Word32
    p i
        | inverse = arrayRead32 ar (17 - i)
        | otherwise = arrayRead32 ar i

-- | Blowfish encrypt a Word using the current state of the key schedule
cipherBlockMutable :: KeySchedule -> Word64 -> IO Word64
cipherBlockMutable (KeySchedule ma) input = doRound input 0
  where
    -- \| Transform the input over 16 rounds
    doRound !i roundIndex
        | roundIndex == 16 = do
            pVal1 <- mutableArrayRead32 ma 16
            pVal2 <- mutableArrayRead32 ma 17
            let final = (fromIntegral pVal1 `shiftL` 32) .|. fromIntegral pVal2
            return $ rotateL (i `xor` final) 32
        | otherwise = do
            pVal <- mutableArrayRead32 ma roundIndex
            let newr = fromIntegral (i `shiftR` 32) `xor` pVal
            newr' <- f newr
            let newi = ((i `shiftL` 32) `xor` newr') .|. fromIntegral newr
            doRound newi (roundIndex + 1)

    -- \| The Blowfish Feistel function F
    f :: Word32 -> IO Word64
    f t = do
        a <- s0 (0xff .&. (t `shiftR` 24))
        b <- s1 (0xff .&. (t `shiftR` 16))
        c <- s2 (0xff .&. (t `shiftR` 8))
        d <- s3 (0xff .&. t)
        return (fromIntegral (((a + b) `xor` c) + d) `shiftL` 32)

    -- \| S-Box arrays, each containing 256 32-bit words
    --   The first 18 words contain the P-Array of subkeys
    s0, s1, s2, s3 :: Word32 -> IO Word32
    s0 i = mutableArrayRead32 ma (fromIntegral i + 18)
    s1 i = mutableArrayRead32 ma (fromIntegral i + 274)
    s2 i = mutableArrayRead32 ma (fromIntegral i + 530)
    s3 i = mutableArrayRead32 ma (fromIntegral i + 786)

iterKeyStream
    :: ByteArrayAccess x
    => x
    -> Word32
    -> Word32
    -> ( Int
         -> Word32
         -> Word32
         -> Word32
         -> Word32
         -> (Word32 -> Word32 -> IO ())
         -> IO ()
       )
    -> IO ()
iterKeyStream x a0 a1 g = f 0 0 a0 a1
  where
    len = B.length x
    -- Avoiding the modulo operation when interating over the ring
    -- buffer is assumed to be more efficient here. All other
    -- implementations do this, too. The branch prediction shall prefer
    -- the branch with the increment.
    n j = if j + 1 >= len then 0 else j + 1
    f i j0 b0 b1 = g i l r b0 b1 (f (i + 2) j8)
      where
        j1 = n j0
        j2 = n j1
        j3 = n j2
        j4 = n j3
        j5 = n j4
        j6 = n j5
        j7 = n j6
        j8 = n j7
        x0 = fromIntegral (B.index x j0)
        x1 = fromIntegral (B.index x j1)
        x2 = fromIntegral (B.index x j2)
        x3 = fromIntegral (B.index x j3)
        x4 = fromIntegral (B.index x j4)
        x5 = fromIntegral (B.index x j5)
        x6 = fromIntegral (B.index x j6)
        x7 = fromIntegral (B.index x j7)
        l = shiftL x0 24 .|. shiftL x1 16 .|. shiftL x2 8 .|. x3
        r = shiftL x4 24 .|. shiftL x5 16 .|. shiftL x6 8 .|. x7
{-# INLINE iterKeyStream #-}

-- Benchmarking shows that GHC considers this function too big to inline
-- although forcing inlining causes an actual improvement.
-- It is assumed that all function calls (especially the continuation)
-- collapse into a tight loop after inlining.