PageRenderTime 398ms CodeModel.GetById 101ms app.highlight 82ms RepoModel.GetById 122ms app.codeStats 0ms

/src/utils/ewgi_util_crypto.erl

http://github.com/skarab/ewgi
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}.