one-time-password 1.0.0.1 → 2.0.0
raw patch · 3 files changed
+72/−71 lines, 3 filesdep +cryptonitedep +memorydep −byteabledep −cryptohashPVP ok
version bump matches the API change (PVP)
Dependencies added: cryptonite, memory
Dependencies removed: byteable, cryptohash
API changes (from Hackage documentation)
- Data.OTP: hotp :: HashAlgorithm a => a -> ByteString -> Word64 -> Word -> Word32
+ Data.OTP: hotp :: forall a key. (HashAlgorithm a, ByteArrayAccess key) => a -> key -> Word64 -> Word -> Word32
- Data.OTP: hotpCheck :: HashAlgorithm a => a -> ByteString -> (Word64, Word64) -> Word64 -> Word -> Word32 -> Bool
+ Data.OTP: hotpCheck :: (HashAlgorithm a, ByteArrayAccess key) => a -> key -> (Word64, Word64) -> Word64 -> Word -> Word32 -> Bool
- Data.OTP: totp :: HashAlgorithm a => a -> ByteString -> UTCTime -> Word64 -> Word -> Word32
+ Data.OTP: totp :: (HashAlgorithm a, ByteArrayAccess key) => a -> key -> UTCTime -> Word64 -> Word -> Word32
- Data.OTP: totpCheck :: HashAlgorithm a => a -> ByteString -> (Word64, Word64) -> UTCTime -> Word64 -> Word -> Word32 -> Bool
+ Data.OTP: totpCheck :: (HashAlgorithm a, ByteArrayAccess key) => a -> key -> (Word64, Word64) -> UTCTime -> Word64 -> Word -> Word32 -> Bool
Files
- CHANGELOG.md +6/−0
- one-time-password.cabal +7/−17
- src/Data/OTP.hs +59/−54
CHANGELOG.md view
@@ -1,5 +1,11 @@ # CHANGELOG +## 2.0.0+### Changed+* Change versioning to semver+* Use `cryptonite` instead of deprecated `cryptohash` (broken backwards+ compatibility)+ ## 1.0.0.1 * Documentation typos fixed
one-time-password.cabal view
@@ -1,8 +1,5 @@ name: one-time-password--- PVP summary: +-+------- breaking API changes--- | | +----- non-breaking API additions--- | | | +--- code changes with no API change-version: 1.0.0.1+version: 2.0.0 synopsis: HMAC-Based and Time-Based One-Time Passwords description: Implements HMAC-Based One-Time Password Algorithm as@@ -11,7 +8,7 @@ license: MIT license-file: LICENSE-copyright: (c) 2012 Artem Leshchev, 2015 Aleksey Uimanov+copyright: (c) 2012 Artem Leshchev, 2016 Aleksey Uimanov author: Artem Leshchev, Aleksey Uimanov maintainer: s9gf4ult@gmail.com <Aleksey Uimanov> homepage: https://github.com/s9gf4ult/one-time-password@@ -30,37 +27,30 @@ library default-language: Haskell2010 hs-source-dirs: src- default-extensions: OverloadedStrings-+ , ScopedTypeVariables exposed-modules: Data.OTP- build-depends: base >= 3 && < 5- , byteable , bytestring , cereal- , cryptohash+ , cryptonite+ , memory , time >= 1.1- ghc-options: -Wall test-suite tests default-language: Haskell2010 type: exitcode-stdio-1.0 hs-source-dirs: test-+ ghc-options: -Wall default-extensions: ExistentialQuantification , OverloadedStrings , RankNTypes- main-is: Test.hs- build-depends: base >= 3 && < 5 , bytestring- , cryptohash+ , cryptonite , one-time-password , tasty , tasty-hunit , time >= 1.1-- ghc-options: -Wall
src/Data/OTP.hs view
@@ -14,9 +14,9 @@ ) where import Crypto.Hash+import Crypto.MAC.HMAC import Data.Bits-import Data.Byteable-import Data.ByteString (ByteString)+import Data.ByteArray (unpack, ByteArrayAccess) import Data.Serialize.Get import Data.Serialize.Put import Data.Time@@ -25,7 +25,7 @@ import qualified Data.ByteString as BS -{- | Compute HOTP using secret key and counter value.+{- | Compute HMAC-Based One-Time Password using secret key and counter value. >>> hotp SHA1 "1234" 100 6 317569@@ -38,28 +38,28 @@ -} -hotp :: (HashAlgorithm a)- => a -- ^ Hashing algorithm from module "Crypto.Hash"- -> ByteString -- ^ Shared secret- -> Word64 -- ^ Counter value- -> Word -- ^ Number of digits in a password- -> Word32 -- ^ HOTP-hotp alg secr cnt len =- let h = trunc- $ toBytes- $ hmacAlg alg secr- $ runPut- $ putWord64be cnt- in h `mod` (10^len)+hotp+ :: forall a key+ . (HashAlgorithm a, ByteArrayAccess key)+ => a -- ^ Hashing algorithm from module "Crypto.Hash.IO"+ -> key -- ^ Shared secret+ -> Word64 -- ^ Counter value+ -> Word -- ^ Number of digits in a password+ -> Word32 -- ^ HOTP+hotp _ key cnt digits =+ let msg = runPut $ putWord64be cnt+ h :: HMAC a+ h = hmac key msg+ w = trunc $ unpack h+ in w `mod` (10 ^ digits) where- trunc :: ByteString -> Word32+ trunc :: [Word8] -> Word32 trunc b =- let offset = BS.last b .&. 15 -- take low 4 bits of last byte- rb = BS.take 4- $ BS.drop (fromIntegral offset) b -- resulting 4 byte value- in case runGet getWord32be rb of- Left e -> error e- Right res -> res .&. (0x80000000 - 1) -- reset highest bit+ let offset = last b .&. 15 -- take low 4 bits of last byte+ rb = BS.pack $ take 4 $ drop (fromIntegral offset) b -- resulting 4 byte value+ in case runGet getWord32be rb of+ Left e -> error e+ Right res -> res .&. (0x80000000 - 1) -- reset highest bit {- | Check presented password against a valid range. @@ -92,20 +92,21 @@ -} -hotpCheck :: (HashAlgorithm a)- => a -- ^ Hashing algorithm- -> ByteString -- ^ Shared secret- -> (Word64, Word64) -- ^ Valid counter range, before and after ideal- -> Word64 -- ^ Ideal (expected) counter value- -> Word -- ^ Number of digits in a password- -> Word32 -- ^ Password entered by user- -> Bool -- ^ True if password is valid+hotpCheck+ :: (HashAlgorithm a, ByteArrayAccess key)+ => a -- ^ Hashing algorithm+ -> key -- ^ Shared secret+ -> (Word64, Word64) -- ^ Valid counter range, before and after ideal+ -> Word64 -- ^ Ideal (expected) counter value+ -> Word -- ^ Number of digits in a password+ -> Word32 -- ^ Password entered by user+ -> Bool -- ^ True if password is valid hotpCheck alg secr rng cnt len pass = let counters = counterRange rng cnt passwds = map (\c -> hotp alg secr c len) counters in any (pass ==) passwds -{- | Compute a TOTP using secret key and time.+{- | Compute a Time-Based One-Time Password using secret key and time. >>> totp SHA1 "1234" (read "2010-10-10 00:01:00 UTC") 30 6 388892@@ -121,13 +122,14 @@ -} -totp :: (HashAlgorithm a)- => a -- ^ Hash algorithm to use- -> ByteString -- ^ Shared secret- -> UTCTime -- ^ Time of TOTP- -> Word64 -- ^ Time period in seconds- -> Word -- ^ Number of digits in a password- -> Word32 -- ^ TOTP+totp+ :: (HashAlgorithm a, ByteArrayAccess key)+ => a -- ^ Hash algorithm to use+ -> key -- ^ Shared secret+ -> UTCTime -- ^ Time of TOTP+ -> Word64 -- ^ Time range in seconds+ -> Word -- ^ Number of digits in a password+ -> Word32 -- ^ TOTP totp alg secr time period len = hotp alg secr (totpCounter time period) len @@ -152,15 +154,16 @@ True -} -totpCheck :: (HashAlgorithm a)- => a -- ^ Hashing algorithm- -> ByteString -- ^ Shared secret- -> (Word64, Word64) -- ^ Valid counter range, before and after ideal- -> UTCTime -- ^ Time of TOTP- -> Word64 -- ^ Period duration in seconds- -> Word -- ^ Numer of digits in a password- -> Word32 -- ^ Password given by user- -> Bool -- ^ True if password is valid+totpCheck+ :: (HashAlgorithm a, ByteArrayAccess key)+ => a -- ^ Hashing algorithm+ -> key -- ^ Shared secret+ -> (Word64, Word64) -- ^ Valid counter range, before and after ideal+ -> UTCTime -- ^ Time of TOTP+ -> Word64 -- ^ Time range in seconds+ -> Word -- ^ Numer of digits in a password+ -> Word32 -- ^ Password given by user+ -> Bool -- ^ True if password is valid totpCheck alg secr rng time period len pass = let counters = totpCounterRange rng time period passwds = map (\c -> hotp alg secr c len) counters@@ -181,9 +184,10 @@ -} -totpCounter :: UTCTime -- ^ Time of totp- -> Word64 -- ^ Period duration in seconds- -> Word64 -- ^ Resulting counter+totpCounter+ :: UTCTime -- ^ Time of totp+ -> Word64 -- ^ Time range in seconds+ -> Word64 -- ^ Resulting counter totpCounter time period = let timePOSIX = floor $ utcTimeToPOSIXSeconds time in timePOSIX `div` period@@ -219,9 +223,10 @@ RFC recommends avoiding excessively large values for counter ranges. -} -counterRange :: (Word64, Word64) -- ^ Number of counters before and after ideal- -> Word64 -- ^ Ideal counter value- -> [Word64]+counterRange+ :: (Word64, Word64) -- ^ Number of counters before and after ideal+ -> Word64 -- ^ Ideal counter value+ -> [Word64] counterRange (tolow', tohigh') ideal = let tolow = min 500 tolow' tohigh = min 499 tohigh'