Skip to content

Commit

Permalink
Update to new major version of elm-sha1 (#5)
Browse files Browse the repository at this point in the history
* Bump elm-sha1 dependency

* Fix typo

* Make elm-verify-examples work

* Use elm-sha1 2.0.0

* Favor use of Bytes over List Int
* Remove unused dependencies

* Use Key/String instead of KeyBytes/MessageBytes

* KeyBytes/MessageBytes didn't make sense given refactorings
* Rename normalizeKey to makeKey (fits better with refactorings)

* Remove unnecessary `Result`s

* `toHex` and `toBase64` can never be `Err`
* Keep hex form in lower case, as is canonical

NOTE: This breaks the API, so is a MAJOR update

* patch version bump

* major version bump

* Add note to README about testing

* Rename toIntList to toByteValues

* Allow for messages and keys to be made from Bytes or Strings
  • Loading branch information
TSFoster authored and rlopzc committed Dec 17, 2019
1 parent 3df91bf commit b4dc325
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 176 deletions.
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

0 comments on commit b4dc325

Please sign in to comment.