Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to new major version of elm-sha1 #5

Merged
merged 12 commits into from
Dec 17, 2019
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elm-stuff
tests/elm-verify-examples.json
/elm-stuff
/tests/VerifyExamples/
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,24 @@ Some API's use the HMAC SHA-1 as Authentication, like Amazon or Twitter.

```elm
import HmacSha1
import HmacSha1.Key as Key exposing (Key)

canonicalString : String
canonicalString =
["application/json", "", "/account", "Wed, 02 Nov 2016 17:26:52 GMT"]
|> String.join ","
String.join ","
[ "application/json"
, ""
, "/account"
, "Wed, 02 Nov 2016 17:26:52 GMT"
]

HmacSha1.digest "verify-secret" canonicalString
|> HmacSha1.toBase64
--> Ok "nLet/JEZG9CRXHScwaQ/na4vsKQ="
appKey : Key
appKey =
Key.fromString "verify-secret"

HmacSha1.fromString appKey canonicalString
|> HmacSha1.toBase64
--> "nLet/JEZG9CRXHScwaQ/na4vsKQ="
```

## Notes
Expand All @@ -43,3 +52,15 @@ cryptographically strong. Use this package to interoperate with systems that
already uses HMAC SHA-1 and not for implementing new systems.

There are stronger cryptographic algorithms like HMAC SHA-2, and [this](https://github.com/ktonon/elm-crypto) Elm package implements it.

## Testing

This package uses doc tests, which can be tested using [elm-verify-examples].
To run all tests (assuming `elm-test` and `elm-verify-examples` are installed):

```bash
cd elm-hmac-sha1
elm-verify-examples --fail-on-warn && elm-test
```

[elm-verify-examples]: https://github.com/stoeffel/elm-verify-examples
15 changes: 7 additions & 8 deletions elm.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
"name": "romariolopezc/elm-hmac-sha1",
"summary": "Compute HMAC with SHA-1 hash function",
"license": "MIT",
"version": "2.0.1",
"version": "3.0.0",
"exposed-modules": [
"HmacSha1"
"HmacSha1",
"HmacSha1.Key"
],
"elm-version": "0.19.0 <= v < 0.20.0",
"dependencies": {
"TSFoster/elm-sha1": "1.0.2 <= v < 2.0.0",
"elm/bytes": "1.0.7 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0",
"ktonon/elm-word": "2.1.2 <= v < 3.0.0",
"waratuman/elm-coder": "3.0.0 <= v < 4.0.0"
"TSFoster/elm-sha1": "2.0.0 <= v < 3.0.0",
"elm/bytes": "1.0.8 <= v < 2.0.0",
"elm/core": "1.0.0 <= v < 2.0.0"
},
"test-dependencies": {
"elm-explorations/test": "1.2.0 <= v < 2.0.0"
}
}
}
243 changes: 90 additions & 153 deletions src/HmacSha1.elm
Original file line number Diff line number Diff line change
@@ -1,213 +1,150 @@
module HmacSha1 exposing
( Digest, digest
, toBytes, toIntList, toHex, toBase64
( Digest, fromString, fromBytes
, toBytes, toByteValues, toHex, toBase64
)

{-| Computes a Hash-based Message Authentication Code (HMAC) using the SHA-1 hash function

@docs Digest, digest
@docs Digest, fromString, fromBytes


# Representation

@docs toBytes, toIntList, toHex, toBase64
@docs toBytes, toByteValues, toHex, toBase64

-}

import Base16
import Base64
import Bitwise
import Bytes exposing (Bytes)
import Bytes exposing (Bytes, Endianness(..))
import Bytes.Decode as Decode exposing (Decoder)
import Bytes.Encode as Encode exposing (Encoder)
import Internals exposing (Key(..), bytesToInts, stringToInts)
import SHA1
import Word.Bytes as Bytes


{-| A HMAC-SHA1 digest.
{-| An HMAC-SHA1 digest.
-}
type Digest
= Digest (List Int)
= Digest SHA1.Digest


{-| Pass a Key and a Message to compute a Digest
{-| Pass a Key and your message as a String to compute a Digest

digest "key" "The quick brown fox jumps over the lazy dog"
import HmacSha1.Key as Key

"The quick brown fox jumps over the lazy dog"
|> fromString (Key.fromString "key")
|> toHex
--> Ok "DE7C9B85B8B78AA6BC8A7A36F70A90701C9DB4D9"
--> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"

digest "key" "The quick brown fox jumps over the lazy dog"
"The quick brown fox jumps over the lazy dog"
|> fromString (Key.fromString "key")
|> toBase64
--> Ok "3nybhbi3iqa8ino29wqQcBydtNk="

-}
digest : String -> String -> Digest
digest key message =
let
normalizedKey =
keyToBytes key
|> normalizeKey

messageBytes =
messageToBytes message
in
hmac normalizedKey messageBytes
|> Digest


{-| Convert a Digest into [elm/bytes](https://package.elm-lang.org/packages/elm/bytes/latest/) Bytes.
You can use this to map it to your own representations. I use it to convert it to
Base16 and Base64 string representations.

toBytes (digest "key" "message")
--> <80 bytes>
--> "3nybhbi3iqa8ino29wqQcBydtNk="

-}
toBytes : Digest -> Bytes
toBytes (Digest data) =
listToBytes data

fromString : Key -> String -> Digest
fromString =
usingEncoder Encode.string

{-| Convert a Digest into a List of Integers. Sometimes you will want to have the
Byte representation as a list of integers.

toIntList (digest "key" "message")
|> toIntList
--> [32,136,223,116,213,242,20,107,72,20,108,175,73,101,55,126,157,11,227,164]

-}
toIntList : Digest -> List Int
toIntList (Digest data) =
data
{-| Pass a Key and your message in Bytes to compute a Digest

import HmacSha1.Key as Key
import Bytes.Encode

{-| Convert a Digest into a base64 String Result

case toBase64 (digest "key" "message") of
Ok base64String ->
"Base64 string: " ++ base64String

Err err ->
"Failed to convert the digest"

--> Base64 string: IIjfdNXyFGtIFGyvSWU3fp0L46Q=

-}
toBase64 : Digest -> Result String String
toBase64 (Digest data) =
Base64.encode data


{-| Convert a Digest into a base16 String Result

case toHex (digest "key" "message") of
Ok base16String ->
"Hex string: " ++ base16String

Err err ->
"Failed to convert the digest"

--> Hex string: 2088DF74D5F2146B48146CAF4965377E9D0BE3A4
Bytes.Encode.sequence []
|> Bytes.Encode.encode
|> fromBytes (Key.fromString "")
|> toBase64
--> "+9sdGxiqbAgyS31ktx+3Y3BpDh0="

-}
toHex : Digest -> Result String String
toHex (Digest data) =
Base16.encode data
fromBytes : Key -> Bytes -> Digest
fromBytes =
usingEncoder Encode.bytes



-- HMAC-SHA1


hmac : KeyBytes -> MessageBytes -> List Int
hmac (KeyBytes key) (MessageBytes message) =
usingEncoder : (message -> Encoder) -> Key -> message -> Digest
usingEncoder encoder (Key key) message =
let
oKeyPad =
List.map (Bitwise.xor 0x5C) key
List.map (Encode.unsignedInt8 << Bitwise.xor 0x5C) key

iKeyPad =
List.map (Bitwise.xor 0x36) key
List.map (Encode.unsignedInt8 << Bitwise.xor 0x36) key
in
List.append iKeyPad message
|> sha1
|> List.append oKeyPad
|> sha1



-- KEY


type KeyBytes
= KeyBytes (List Int)


keyToBytes : String -> KeyBytes
keyToBytes key =
KeyBytes (Bytes.fromUTF8 key)


normalizeKey : KeyBytes -> KeyBytes
normalizeKey (KeyBytes key) =
case compare blockSize <| List.length key of
EQ ->
KeyBytes key

GT ->
padEnd key
|> KeyBytes

LT ->
sha1 key
|> padEnd
|> KeyBytes


padEnd : List Int -> List Int
padEnd bytes =
List.append bytes <|
List.repeat (blockSize - List.length bytes) 0

[ Encode.sequence iKeyPad, encoder message ]
|> Encode.sequence
|> Encode.encode
|> SHA1.fromBytes
|> SHA1.toBytes
|> Encode.bytes
|> List.singleton
|> (::) (Encode.sequence oKeyPad)
|> Encode.sequence
|> Encode.encode
|> SHA1.fromBytes
|> Digest


-- MESSAGE
{-| Convert a Digest into [elm/bytes](https://package.elm-lang.org/packages/elm/bytes/latest/) Bytes.
You can use this to map it to your own representations. I use it to convert it to
Base16 and Base64 string representations.

import Bytes
import HmacSha1.Key as Key

type MessageBytes
= MessageBytes (List Int)
fromString (Key.fromString "key") "message"
|> toBytes
|> Bytes.width
--> 20

-}
toBytes : Digest -> Bytes
toBytes (Digest data) =
SHA1.toBytes data

messageToBytes : String -> MessageBytes
messageToBytes message =
MessageBytes (Bytes.fromUTF8 message)

{-| Convert a Digest into a List of Integers. Each Integer is in the range
0-255, and represents one byte. Can be useful for passing digest on to other
packages that make use of this convention.

import HmacSha1.Key as Key

-- SHA 1
fromString (Key.fromString "key") "message"
|> toByteValues
--> [32, 136, 223, 116, 213, 242, 20, 107, 72, 20, 108, 175, 73, 101, 55, 126, 157, 11, 227, 164]

-}
toByteValues : Digest -> List Int
toByteValues (Digest data) =
SHA1.toByteValues data

blockSize : Int
blockSize =
64

{-| Convert a Digest into a base64 String

sha1 : List Int -> List Int
sha1 bytes =
bytes
|> SHA1.fromBytes
|> SHA1.toBytes
import HmacSha1.Key as Key

fromString (Key.fromString "key") "message"
|> toBase64
--> "IIjfdNXyFGtIFGyvSWU3fp0L46Q="

-}
toBase64 : Digest -> String
toBase64 (Digest data) =
SHA1.toBase64 data

-- elm/bytes
-- ENCODE

{-| Convert a Digest into a base16 String

listToBytes : List Int -> Bytes
listToBytes byteList =
Encode.sequence (List.map intEncoder byteList)
|> Encode.encode
import HmacSha1.Key as Key

fromString (Key.fromString "key") "message"
|> toHex
--> "2088df74d5f2146b48146caf4965377e9d0be3a4"

intEncoder : Int -> Encode.Encoder
intEncoder int =
Encode.unsignedInt32 Bytes.BE int
-}
toHex : Digest -> String
toHex (Digest data) =
SHA1.toHex data
Loading