%% @author Jared Flatow, Steve Vinoski
%% @doc HMAC wrappers for erlsha2
%% @reference
%% Based on the
%%
%% mailing list response
%%
%% by Steve Davis.
-module(hmac).
-export([hexlify/1,
hexlify/2,
hmac/2,
hmac/4,
hmac224/2,
hmac256/2,
hmac384/2,
hmac512/2,
hmac512_256/2]).
-version("2.2.2").
-ifdef(USE_CRYPTO_SHA_MAC).
-define(CRYPTO_HMAC(Key,Data), crypto:sha_mac(Key,Data)).
-else.
-define(CRYPTO_HMAC(Key,Data), crypto:mac(hmac,sha,Key,Data)).
-endif.
-define(SHA_224_BLOCKSIZE, 64).
-define(SHA_256_BLOCKSIZE, 64).
-define(SHA_384_BLOCKSIZE, 128).
-define(SHA_512_BLOCKSIZE, 128).
-define(HMAC_STRING, 1).
-define(HMAC_UPPER, 2).
-define(DEFAULT_HMAC_FLAGS, (?HMAC_STRING bor ?HMAC_UPPER)).
%% @spec hexlify(binary()) -> list()
%% @doc Convert binary to equivalent hexadecimal string.
%%
hexlify(Binary) when is_binary(Binary) ->
hexlify_nif(Binary, ?DEFAULT_HMAC_FLAGS).
%% @spec hexlify(binary(), hmac_opts()) -> string() | binary()
%% @doc Convert binary to equivalent hexadecimal string or binary depending
%% on the options passed in the second argument. If the options list
%% contains the atom 'string' a string is returned, or if the option
%% 'binary' is passed a binary is returned. If the options list contains
%% the atom 'upper' then the alphabetic hexadecimal characters in the
%% return value are uppercase, or if the option 'lower' is passed then the
%% alphabetic hexadecimal characters in the return value are lowercase. The
%% default if no options are passed is to return an uppercase string.
%%
hexlify(Binary, []) when is_binary(Binary) ->
hexlify_nif(Binary, ?DEFAULT_HMAC_FLAGS);
hexlify(Binary, [string,upper]) when is_binary(Binary) ->
hexlify_nif(Binary, ?DEFAULT_HMAC_FLAGS);
hexlify(Binary, [upper,string]) when is_binary(Binary) ->
hexlify_nif(Binary, ?DEFAULT_HMAC_FLAGS);
hexlify(Binary, [string,lower]) when is_binary(Binary) ->
hexlify_nif(Binary, ?HMAC_STRING);
hexlify(Binary, [lower,string]) when is_binary(Binary) ->
hexlify_nif(Binary, ?HMAC_STRING);
hexlify(Binary, [binary,upper]) when is_binary(Binary) ->
hexlify_nif(Binary, ?HMAC_UPPER);
hexlify(Binary, [upper,binary]) when is_binary(Binary) ->
hexlify_nif(Binary, ?HMAC_UPPER);
hexlify(Binary, [binary,lower]) when is_binary(Binary) ->
hexlify_nif(Binary, 0);
hexlify(Binary, [lower,binary]) when is_binary(Binary) ->
hexlify_nif(Binary, 0);
hexlify(Binary, Opts) when is_binary(Binary), is_list(Opts) ->
Flags = lists:foldl(fun(string, Acc) ->
Acc bor ?HMAC_STRING;
(binary, Acc) ->
Acc band bnot ?HMAC_STRING;
(upper, Acc) ->
Acc bor ?HMAC_UPPER;
(lower, Acc) ->
Acc band bnot ?HMAC_UPPER
end, ?DEFAULT_HMAC_FLAGS, Opts),
hexlify_nif(Binary, Flags).
hexlify_nif(_Bin, 0) -> string:lowercase(binary:encode_hex(_Bin));
hexlify_nif(_Bin, ?HMAC_STRING) -> binary:bin_to_list(hexlify_nif(_Bin, 0));
hexlify_nif(_Bin, ?HMAC_UPPER) -> string:uppercase(binary:encode_hex(_Bin));
hexlify_nif(_Bin, 3) -> binary:bin_to_list(hexlify_nif(_Bin, ?HMAC_UPPER)).
%% @spec hmac224(key(), data()) -> mac()
%% where
%% key() = iolist() | binary()
%% data() = iolist() | binary()
%% mac() = binary()
%% @doc Compute a SHA-224 MAC message authentication code from key and data.
%%
hmac224(Key, Data) ->
hmac(Key, Data, fun erlsha2:sha224/1, ?SHA_224_BLOCKSIZE).
%% @spec hmac256(key(), data()) -> mac()
%% where
%% key() = iolist() | binary()
%% data() = iolist() | binary()
%% mac() = binary()
%% @doc Compute a SHA-256 MAC message authentication code from key and data.
%%
hmac256(Key, Data) ->
hmac(Key, Data, fun erlsha2:sha256/1, ?SHA_256_BLOCKSIZE).
%% @spec hmac384(key(), data()) -> mac()
%% where
%% key() = iolist() | binary()
%% data() = iolist() | binary()
%% mac() = binary()
%% @doc Compute a SHA-384 MAC message authentication code from key and data.
%%
hmac384(Key, Data) ->
hmac(Key, Data, fun erlsha2:sha384/1, ?SHA_384_BLOCKSIZE).
%% @spec hmac512(key(), data()) -> mac()
%% where
%% key() = iolist() | binary()
%% data() = iolist() | binary()
%% mac() = binary()
%% @doc Compute a SHA-512 MAC message authentication code from key and data.
%%
hmac512(Key, Data) ->
hmac(Key, Data, fun erlsha2:sha512/1, ?SHA_512_BLOCKSIZE).
%% @spec hmac512(key(), data()) -> mac()
%% where
%% key() = iolist() | binary()
%% data() = iolist() | binary()
%% mac() = binary()
%% @doc Compute a SHA-512/256 MAC message authentication code from key and data.
%%
hmac512_256(Key, Data) ->
hmac(Key, Data, fun erlsha2:sha512_256/1, ?SHA_512_BLOCKSIZE).
%% @spec hmac(key(), data()) -> mac()
%% where
%% key() = iolist() | binary()
%% data() = iolist() | binary()
%% mac() = binary()
%% @doc Compute a SHA MAC message authentication code from key and data.
%%
hmac(Key, Data) ->
?CRYPTO_HMAC(Key, Data).
%% @spec hmac(key(), data(), hash(), blocksize()) -> mac()
%% where
%% key() = iolist() | binary()
%% data() = iolist() | binary()
%% hash() = fun((binary()) -> binary())
%% blocksize() = non_neg_integer()
%% mac() = binary()
%% @doc Compute a SHA MAC message authentication code from key and data using
%% the specified hash function and blocksize.
%%
hmac(Key, Data, Hash, Blocksize) when is_list(Key) ->
hmac(iolist_to_binary(Key), Data, Hash, Blocksize);
hmac(Key, Data, Hash, Blocksize) when is_list(Data) ->
hmac(Key, iolist_to_binary(Data), Hash, Blocksize);
hmac(Key, Data, Hash, Blocksize) when is_binary(Key), is_binary(Data) ->
HashKey =
case Blocksize - byte_size(Key) of
X when X < 0 ->
KeyDigest = Hash(Key),
Pad = Blocksize - byte_size(KeyDigest),
<>;
X when X > 0 ->
<>;
X when X =:= 0 ->
Key
end,
IPad = binary:copy(<<16#36>>, Blocksize),
OPad = binary:copy(<<16#5c>>, Blocksize),
HVal = Hash(<<(crypto:exor(HashKey, IPad))/binary, Data/binary>>),
Hash(<<(crypto:exor(HashKey, OPad))/binary, HVal/binary>>).