/src/utils/ewgi_util_crypto.erl
Erlang | 69 lines | 36 code | 6 blank | 27 comment | 0 complexity | 8b0007d2222b69cfe83a7fa60d2f9112 MD5 | raw file
1%% @author Hunter Morris <hunter.morris@smarkets.com> 2%% @author Davide Marquęs <nesrait@gmail.com> 3%% @copyright 2009 Smarkets Limited. 4%% 5%% @doc Secure data transformations for data storage in unsecure locations. 6%% Based on smak_sn_cookie by Hunter Morris. 7%% Differences from smak_sn_cookie: removed the timestamping operation on 8%% data encoding and and the timeout validation on decoding data. 9%% 10%% The binary data is first compressed, then encrypted using AES in CFB mode 11%% with a random initialization vector. After that the data is signed 12%% using HMAC-SHA1 with the same key. 13%% 14%% When the data is decoded, the signature is first checked for validity and 15%% then the data is decrypted, decompressed, and deserialized. 16%% 17%% @end 18%% 19%% Licensed under the MIT license: 20%% http://www.opensource.org/licenses/mit-license.php 21 22-module(ewgi_util_crypto). 23-author('Hunter Morris <hunter.morris@smarkets.com>'). 24-author('Davide Marquęs <nesrait@gmail.com>'). 25 26-define(MAX_SIZE, 4096). %% Most browsers only allow 4K of cookies 27-define(MAX_SIZE_REPR, ?MAX_SIZE/2). %% Assume a 2-character representation of each byte in the final cookie. 28 29-export([encode/2, encode/3, decode/2]). 30 31%% @spec encode(Key::binary(), Data::binary()) -> binary() | {error, Reason::atom()} 32%% @doc Encodes and signs the data using the specified secret key. 33%% @see encode/3 34-spec encode(binary(), binary()) -> binary() | {error, atom()}. 35encode(Key, Data) -> 36 encode(Key, Data, ?MAX_SIZE_REPR). 37 38%% @spec encode(Key::binary(), Data::binary(), MaxSize::integer()) -> binary() | {error, Reason::atom()} 39%% @doc Encodes and signs the data using the specified secret key and provides an error if it exceeds the maximum size. 40-spec encode(binary(), binary(), integer()) -> binary() | {error, atom()}. 41encode(<<Key:16/binary>>, Data, MaxSize) when MaxSize > 0, is_binary(Data) -> 42 IV = crypto:rand_bytes(16), 43 DataCompressed = zlib:zip(Data), 44 DataCrypted = crypto:aes_cfb_128_encrypt(Key, IV, DataCompressed), 45 HMACSignature = crypto:sha_mac(Key, <<IV:16/binary, DataCrypted/bits>>), 46 Result = <<HMACSignature:20/binary, IV:16/binary, DataCrypted/bits>>, 47 case size(Result) of 48 Sz when Sz >= MaxSize -> 49 {error, session_too_large}; 50 _ -> 51 Result 52 end; 53encode(_Key, _, _) -> 54 {error, invalid_key}. 55 56%% @spec decode(Key::binary(), Cookie::binary()) -> binary() | {error, Reason::atom()} 57%% @doc Checks the signature and timeout and decrypts the cookie if it is valid. 58-spec decode(binary(), binary()) -> binary() | {error, atom()}. 59decode(<<Key:16/binary>>, <<HMACSignature:20/binary, IV:16/binary, DataCrypted/bits>>) -> 60 case crypto:sha_mac(Key, <<IV:16/binary, DataCrypted/bits>>) of 61 HMACSignature -> 62 DataCompressed = crypto:aes_cfb_128_decrypt(Key, IV, DataCrypted), 63 Data = zlib:unzip(DataCompressed), 64 Data; 65 _ -> 66 {error, data_tampered} 67 end; 68decode(_Key, _) -> 69 {error, invalid_key}.