2011-11-12 15 views
5

Ho pensato di provare a implementare SHA1 in Haskell. Ho trovato un'implementazione che compila e restituisce la risposta giusta per la stringa nulla (""), ma nient'altro. Non riesco a capire cosa potrebbe essere sbagliato. Qualcuno può familiarizzare con l'algoritmo e SHA1 lo fa notare?SHA1 in Haskell - qualcosa di sbagliato con la mia implementazione

import Data.Bits 
import Data.Int 
import Data.List 
import Data.Word 
import Text.Printf 
import qualified Data.ByteString.Lazy as L 
import qualified Data.ByteString.Lazy.Char8 as C 

h0 = 0x67452301 :: Word32 
h1 = 0xEFCDAB89 :: Word32 
h2 = 0x98BADCFE :: Word32 
h3 = 0x10325476 :: Word32 
h4 = 0xC3D2E1F0 :: Word32 

sha1string :: String -> String 
sha1string s = concat $ map (printf "%02x") $ sha1 . C.pack $ s 

sha1 :: L.ByteString -> [Word8] 
sha1 msg = concat [w32ToComps a, w32ToComps b, w32ToComps c, w32ToComps d, w32ToComps e] 
    where (a, b, c, d, e) = sha1' msg 0 h0 h1 h2 h3 h4 

sha1' msg sz a b c d e 
    | L.length m1 < 64 = sha1'last (padded msg sz) a b c d e 
    | otherwise  = uncurry5 (sha1' m2 (sz + 64)) $ whole a b c d e m1 
    where (m1, m2) = L.splitAt 64 msg 

sha1'last msg a b c d e 
    | m1 == L.empty = (a, b, c, d, e) 
    | otherwise  = uncurry5 (sha1'last m2) $ whole a b c d e m1 
    where (m1, m2) = L.splitAt 64 msg 

whole a b c d e msg = partcd (partab msg) a b c d e 

partcd ws a b c d e = (h0 + a', h1 + b', h2 + c', h3 + d', h4 + e') 
    where 
    (a', b', c', d', e') = go ws a b c d e 0 
    go ws a b c d e 80 = (a, b, c, d, e) 
    go (w:ws) a b c d e t = go ws temp a (rotate b 30) c d (t+1) 
     where temp = (rotate a 5) + f t b c d + e + w + k t 

partab chunk = take 80 ns 
    where 
    ns  = initial ++ zipWith4 g (drop 13 ns) (drop 8 ns) (drop 2 ns) ns 
    g a b c d = rotate (a `xor` b `xor` c `xor` d) 1 
    initial = map (L.foldl (\a b -> (a * 256) + fromIntegral b) 0) $ paginate 4 chunk 

f t b c d 
    | t >= 0 && t <= 19 = (b .&. c) .|. ((complement b) .&. d) 
    | t >= 20 && t <= 39 = b `xor` c `xor` d 
    | t >= 40 && t <= 59 = (b .&. c) .|. (b .&. d) .|. (c .&. d) 
    | t >= 60 && t <= 79 = b `xor` c `xor` d 

k t 
    | t >= 0 && t <= 19 = 0x5A827999 
    | t >= 20 && t <= 39 = 0x6ED9EBA1 
    | t >= 40 && t <= 59 = 0x8F1BBCDC 
    | t >= 60 && t <= 79 = 0xCA62C1D6 

padded msg prevsz = L.append msg (L.pack pad) 
    where 
    sz  = L.length msg 
    totalsz = prevsz + sz 
    padsz = fromIntegral $ (128 - 9 - sz) `mod` 64 
    pad  = [0x80] ++ (replicate padsz 0) ++ int64ToComps totalsz 

uncurry5 f (a, b, c, d, e) = f a b c d e 

paginate n xs 
    | xs == L.empty = [] 
    | otherwise  = let (a, b) = L.splitAt n xs in a : paginate n b 

w32ToComps :: Word32 -> [Word8] 
w32ToComps = integerToComps [24, 16 .. 0] 

int64ToComps :: Int64 -> [Word8] 
int64ToComps = integerToComps [56, 48 .. 0] 

integerToComps :: (Integral a, Bits a) => [Int] -> a -> [Word8] 
integerToComps bits x = map f bits 
    where f n = fromIntegral ((x `shiftR` n) .&. 0xff) :: Word8 
+0

Durante il debug, è molto utile se si può restringere il problema alla funzione più profonda nello stack di chiamate che fa qualcosa di inaspettato. Puoi provare a effettuare alcune chiamate alle altre funzioni in ghci e verificare che stiano calcolando ciò che ti aspetti che calcolino? –

risposta

9

Per cominciare, si sembrano essere mantenendo un conteggio dimensione in byte (vedi sz + 64), ma il conteggio che viene aggiunto dovrebbe essere in bit, quindi è necessario moltiplicare per 8 da qualche parte (per inciso, ti suggerisco di usare cereal o binary invece di far ruotare il proprio intero su big endian Word64). Questo non è l'unico problema però.

EDIT: trovato

Ah-ah! Non dimenticare mai, wikipedia è scritta da un gruppo di imperativi, mutabili mondo non illuminati! Completa ogni blocco con h0 + a', h1 + b', ... ma quello dovrebbe essere il vecchio contesto più i nuovi valori: a + a', b + b', .... Tutto si risolve dopo quella (e la dimensione sopra) risolta.

Il codice di prova viene completato con 5 test di proprietà e 129 KAT successivi.

Fine Edit

che vi aiutano un sacco se diviso l'implementazione nel normale iniziale, aggiornamento, le operazioni di finalizzare. In questo modo puoi confrontare i risultati intermedi con altre implementazioni.

Ho appena creato il codice di test per l'implementazione utilizzando crypto-api-tests. Il codice aggiuntivo è sotto se sei interessato, non dimenticare di installare crypto-api-tests.

import Test.SHA 
import Test.Crypto 
import Crypto.Classes 
import Data.Serialize 
import Data.Tagged 
import Control.Monad 

main = defaultMain =<< makeSHA1Tests (undefined :: SHA1) 

data SHA1 = SHA1 [Word8] 
    deriving (Eq, Ord, Show) 
data CTX = CTX L.ByteString 
instance Serialize SHA1 where 
    get = liftM SHA1 (mapM (const get) [1..20]) 
    put (SHA1 x) = mapM_ put x 

instance Hash CTX SHA1 where 
    outputLength = Tagged 160 
    blockLength = Tagged (64*8) 
    initialCtx = CTX L.empty 
    updateCtx (CTX m) x = CTX (L.append m (L.fromChunks [x])) 
    finalize (CTX m) b = SHA1 $ sha1 (L.append m (L.fromChunks [b])) 
+0

È incredibile, Thomas. Grazie mille :) – Ana

Problemi correlati