353 lines
13 KiB
Erlang
353 lines
13 KiB
Erlang
%%% @doc Module enacl implements bindings to the NaCl/libsodium crypto-library
|
|
%%% <p>This module implements NIF bindings to the library known as NaCl (pronounced "salt").
|
|
%%% The NaCl library provides a sane cryptographic interface to the world in an attempt to
|
|
%%% make it harder to abuse and misuse cryptographic primitives.</p>
|
|
%%% <p>This module implements an Erlang-idiomatic API to the underlying library. If in doubt
|
|
%%% about a primitive, always consult the underlying documentation.</p>
|
|
%%% <p>There are two libraries in existence: NaCl and libsodium, the latter being a more
|
|
%%% portable variant of the NaCl library. The C-level API is interchangeable so we can run
|
|
%%% on any of these underlying libraries as seen from the Erlang world. We simply have to
|
|
%%% restrict ourselves to the portion of the code base which is overlapping.</p>
|
|
%%% <p><b>Warning:</b> The cryptographic strength of your implementation is no stronger than
|
|
%%% plaintext cryptography unless you take care in using these primitives correctly. Hence,
|
|
%%% implementors should use these primitives with that in mind.</p>
|
|
%%% <p><b>Note:</b>All functions will fail with a `badarg' error if given incorrect
|
|
%%% parameters.</p>
|
|
%%% @end.
|
|
-module(enacl).
|
|
|
|
%% Public key crypto
|
|
-export([
|
|
box_keypair/0,
|
|
box/4,
|
|
box_open/4,
|
|
box_nonce_size/0,
|
|
box_public_key_bytes/0,
|
|
box_secret_key_bytes/0,
|
|
|
|
sign_keypair_public_size/0,
|
|
sign_keypair_secret_size/0,
|
|
sign_keypair/0,
|
|
sign/2,
|
|
sign_open/2
|
|
]).
|
|
|
|
%% Secret key crypto
|
|
-export([
|
|
secretbox_key_size/0,
|
|
secretbox_nonce_size/0,
|
|
secretbox/3,
|
|
secretbox_open/3,
|
|
|
|
stream_key_size/0,
|
|
stream_nonce_size/0,
|
|
stream/3,
|
|
stream_xor/3,
|
|
|
|
auth_key_size/0,
|
|
auth_size/0,
|
|
auth/2,
|
|
auth_verify/3,
|
|
|
|
onetime_auth_key_size/0,
|
|
onetime_auth_size/0,
|
|
onetime_auth/2,
|
|
onetime_auth_verify/3
|
|
]).
|
|
|
|
%% Low-level functions
|
|
-export([
|
|
hash/1,
|
|
verify_16/2,
|
|
verify_32/2
|
|
]).
|
|
|
|
%% Other helper functions
|
|
-export([
|
|
reds/1
|
|
]).
|
|
|
|
%% Count reductions and number of scheduler yields for Fun. Fun is assumed
|
|
%% to be one of the above exor variants.
|
|
reds(Fun) ->
|
|
Parent = self(),
|
|
Pid = spawn(fun() ->
|
|
Self = self(),
|
|
Start = os:timestamp(),
|
|
R0 = process_info(Self, reductions),
|
|
Fun(),
|
|
R1 = process_info(Self, reductions),
|
|
T = timer:now_diff(os:timestamp(), Start),
|
|
Parent ! {Self,{T, R1, R0}} end),
|
|
receive
|
|
{Pid,Result} ->
|
|
Result
|
|
end.
|
|
|
|
%% Low level helper functions
|
|
%% -----------------
|
|
|
|
%% @doc hash/1 hashes data into a cryptographically secure checksum.
|
|
%% <p>Given a binary, `Data' of any size, run a cryptographically secure hash algorithm to
|
|
%% produce a checksum of the data. This can be used to verify the integrity of a data block
|
|
%% since the checksum have the properties of cryptographic hashes in general.</p>
|
|
%% <p>The currently selected primitive (Nov. 2014) is SHA-512</p>
|
|
%% @end
|
|
-spec hash(Data) -> Checksum
|
|
when Data :: binary(),
|
|
Checksum :: binary().
|
|
|
|
hash(Bin) -> enacl_nif:crypto_hash(Bin).
|
|
|
|
%% @doc verify_16/2 implements constant time 16-byte string verification
|
|
%% <p>A subtle problem in cryptographic software are timing attacks where an attacker exploits
|
|
%% early exist in string verification if the strings happen to mismatch. This allows the
|
|
%% attacker to time how long verification took and thus learn the structure of the desired
|
|
%% string to use. The verify_16/2 call will check two 16 byte strings for equality while
|
|
%% guaranteeing the equality operation is constant time.</p>
|
|
%% <p>If the strings are not exactly 16 bytes, the comparison function will fail with badarg.</p>
|
|
%% <p>Verification returns a boolean. `true' if the strings match, `false' otherwise.</p>
|
|
%% @end
|
|
-spec verify_16(binary(), binary()) -> boolean().
|
|
verify_16(X, Y) -> enacl_nif:crypto_verify_16(X, Y).
|
|
|
|
%% @doc verify_32/2 implements constant time 32-byte string verification
|
|
%% This function works as {@link verify_16/2} but does so on 32 byte strings.
|
|
%% @end
|
|
-spec verify_32(binary(), binary()) -> boolean().
|
|
verify_32(X, Y) -> enacl_nif:crypto_verify_32(X, Y).
|
|
|
|
%% Public Key Crypto
|
|
%% ---------------------
|
|
%% @doc box_keypair/0 creates a new Public/Secret keypair.
|
|
%% Generates and returns a new key pair for the Box encryption scheme. The return value is a
|
|
%% map in order to avoid using the public key as a secret key and vice versa.
|
|
%% @end.
|
|
-spec box_keypair() -> maps:map(atom(), binary()).
|
|
box_keypair() ->
|
|
{PK, SK} = enacl_nif:crypto_box_keypair(),
|
|
#{ public => PK, secret => SK}.
|
|
|
|
%% @doc box/4 encrypts+authenticates a message to another party.
|
|
%% Encrypt a `Msg` to the party identified by public key `PK` using your own secret key `SK` to
|
|
%% authenticate yourself. Requires a `Nonce` in addition. Returns the ciphered message.
|
|
%% @end
|
|
-spec box(Msg, Nonce, PK, SK) -> CipherText
|
|
when Msg :: binary(),
|
|
Nonce :: binary(),
|
|
PK :: binary(),
|
|
SK :: binary(),
|
|
CipherText :: binary().
|
|
box(Msg, Nonce, PK, SK) ->
|
|
enacl_nif:crypto_box([p_zerobytes(), Msg], Nonce, PK, SK).
|
|
|
|
%% @doc box_open/4 decrypts+verifies a message from another party.
|
|
%% Decrypt a `CipherText` into a `Msg` given the other partys public key `PK` and your secret
|
|
%% key `SK`. Also requires the same nonce as was used by the other party. Returns the plaintext
|
|
%% message.
|
|
-spec box_open(CipherText, Nonce, PK, SK) -> Msg
|
|
when CipherText :: binary(),
|
|
Nonce :: binary(),
|
|
PK :: binary(),
|
|
SK :: binary(),
|
|
Msg :: binary().
|
|
box_open(CipherText, Nonce, PK, SK) ->
|
|
case enacl_nif:crypto_box_open([p_box_zerobytes(), CipherText], Nonce, PK, SK) of
|
|
{error, Err} -> {error, Err};
|
|
Bin when is_binary(Bin) -> {ok, Bin}
|
|
end.
|
|
|
|
%% @doc box_nonce_size/0 return the byte-size of the nonce
|
|
%% Used to obtain the size of the nonce.
|
|
%% @end.
|
|
-spec box_nonce_size() -> pos_integer().
|
|
box_nonce_size() ->
|
|
enacl_nif:crypto_box_NONCEBYTES().
|
|
|
|
%% @private
|
|
-spec box_public_key_bytes() -> pos_integer().
|
|
box_public_key_bytes() ->
|
|
enacl_nif:crypto_box_PUBLICKEYBYTES().
|
|
|
|
%% Signatures
|
|
|
|
%% @private
|
|
sign_keypair_public_size() ->
|
|
enacl_nif:crypto_sign_PUBLICKEYBYTES().
|
|
|
|
%% @private
|
|
sign_keypair_secret_size() ->
|
|
enacl_nif:crypto_sign_SECRETKEYBYTES().
|
|
|
|
%% @doc sign_keypair/0 returns a signature keypair for signing
|
|
%% The returned value is a map in order to make it harder to misuse keys.
|
|
%% @end
|
|
-spec sign_keypair() -> KeyMap
|
|
when KeyMap :: maps:map(atom(), binary()).
|
|
sign_keypair() ->
|
|
{PK, SK} = enacl_nif:crypto_sign_keypair(),
|
|
#{ public => PK, secret => SK}.
|
|
|
|
%% @doc sign/2 signs a message with a digital signature identified by a secret key.
|
|
%% Given a message `M' and a secret key `SK' the function will sign the message and return a signed message `SM'.
|
|
%% @end
|
|
-spec sign(M, SK) -> SM
|
|
when
|
|
M :: binary(),
|
|
SK :: binary(),
|
|
SM :: binary().
|
|
sign(M, SK) -> enacl_nif:crypto_sign(M, SK).
|
|
|
|
%% @doc sign_open/2 opens a digital signature
|
|
%% Given a signed message `SM' and a public key `PK', verify that the message has the right signature. Returns either
|
|
%% `{ok, M}' or `{error, failed_verification}' depending on the correctness of the signature.
|
|
%% @end
|
|
-spec sign_open(SM, PK) -> {ok, M} | {error, failed_verification}
|
|
when
|
|
SM :: binary(),
|
|
PK :: binary(),
|
|
M :: binary().
|
|
sign_open(SM, PK) ->
|
|
case enacl_nif:crypto_sign_open(SM, PK) of
|
|
M when is_binary(M) -> {ok, M};
|
|
{error, Err} -> {error, Err}
|
|
end.
|
|
|
|
%% @private
|
|
-spec box_secret_key_bytes() -> pos_integer().
|
|
box_secret_key_bytes() ->
|
|
enacl_nif:crypto_box_SECRETKEYBYTES().
|
|
|
|
secretbox(Msg, Nonce, Key) ->
|
|
enacl_nif:crypto_secretbox([s_zerobytes(), Msg], Nonce, Key).
|
|
|
|
secretbox_open(CipherText, Nonce, Key) ->
|
|
case enacl_nif:crypto_secretbox_open([s_box_zerobytes(), CipherText], Nonce, Key) of
|
|
{error, Err} -> {error, Err};
|
|
Bin when is_binary(Bin) -> {ok, Bin}
|
|
end.
|
|
|
|
secretbox_nonce_size() ->
|
|
enacl_nif:crypto_secretbox_NONCEBYTES().
|
|
|
|
secretbox_key_size() ->
|
|
enacl_nif:crypto_secretbox_KEYBYTES().
|
|
|
|
%% @doc stream_nonce_size/0 returns the byte size of the nonce for streams
|
|
%% @end
|
|
-spec stream_nonce_size() -> pos_integer().
|
|
stream_nonce_size() -> enacl_nif:crypto_stream_NONCEBYTES().
|
|
|
|
%% @doc stream_key_size/0 returns the byte size of the key for streams
|
|
%% @end
|
|
-spec stream_key_size() -> pos_integer().
|
|
stream_key_size() -> enacl_nif:crypto_stream_KEYBYTES().
|
|
|
|
%% @doc stream/3 produces a cryptographic stream suitable for secret-key encryption
|
|
%% <p>Given a positive `Len' a `Nonce' and a `Key', the stream/3 function will return an unpredictable cryptographic stream of bytes
|
|
%% based on this output. In other words, the produced stream is indistinguishable from a random stream. Using this stream one
|
|
%% can XOR it with a message in order to produce a encrypted message.</p>
|
|
%% <p><b>Note:</b> You need to use different Nonce values for different messages. Otherwise the same stream is produced and thus
|
|
%% the messages will have predictability which in turn makes the encryption scheme fail.</p>
|
|
%% @end
|
|
-spec stream(Len, Nonce, Key) -> CryptoStream
|
|
when
|
|
Len :: non_neg_integer(),
|
|
Nonce :: binary(),
|
|
Key :: binary(),
|
|
CryptoStream :: binary().
|
|
stream(Len, Nonce, Key) when is_integer(Len), Len >= 0 ->
|
|
enacl_nif:crypto_stream(Len, Nonce, Key);
|
|
stream(_, _, _) -> error(badarg).
|
|
|
|
%% @doc stream_xor/3 encrypts a plaintext message into ciphertext
|
|
%% The stream_xor/3 function works by using the {@link stream/3} api to XOR a message with the cryptographic stream. The same
|
|
%% caveat applies: the nonce must be new for each sent message or the system fails to work.
|
|
%% @end
|
|
-spec stream_xor(Msg, Nonce, Key) -> CipherText
|
|
when
|
|
Msg :: binary(),
|
|
Nonce :: binary(),
|
|
Key :: binary(),
|
|
CipherText :: binary().
|
|
stream_xor(Msg, Nonce, Key) ->
|
|
enacl_nif:crypto_stream_xor(Msg, Nonce, Key).
|
|
|
|
%% @doc auth_key_size/0 returns the byte-size of the authentication key
|
|
%% @end
|
|
-spec auth_key_size() -> pos_integer().
|
|
auth_key_size() -> enacl_nif:crypto_auth_KEYBYTES().
|
|
|
|
%% @doc auth_size/0 returns the byte-size of the authenticator
|
|
%% @end
|
|
-spec auth_size() -> pos_integer().
|
|
auth_size() -> enacl_nif:crypto_auth_BYTES().
|
|
|
|
%% @doc auth/2 produces an authenticator (MAC) for a message
|
|
%% Given a `Msg' and a `Key' produce a MAC/Authenticator for that message. The key can be reused for several such Msg/Authenticator pairs.
|
|
%% An eavesdropper will not learn anything extra about the message structure.
|
|
%% @end
|
|
-spec auth(Msg, Key) -> Authenticator
|
|
when
|
|
Msg :: binary(),
|
|
Key :: binary(),
|
|
Authenticator :: binary().
|
|
auth(Msg, Key) -> enacl_nif:crypto_auth(Msg, Key).
|
|
|
|
%% @doc auth_verify/3 verifies an authenticator for a message
|
|
%% Given an `Authenticator', a `Msg' and a `Key'; verify that the MAC for the pair `{Msg, Key}' is really `Authenticator'. Returns
|
|
%% the value `true' if the verfication passes. Upon failure, the function returns `false'.
|
|
%% @end
|
|
-spec auth_verify(Authenticator, Msg, Key) -> boolean()
|
|
when
|
|
Authenticator :: binary(),
|
|
Msg :: binary(),
|
|
Key :: binary().
|
|
auth_verify(A, M, K) -> enacl_nif:crypto_auth_verify(A, M, K).
|
|
|
|
%% @doc onetime_auth/2 produces a ONE-TIME authenticator for a message
|
|
%% This function works like {@link auth/2} except that the key must not be used again for subsequent messages. That is, the pair
|
|
%% `{Msg, Key}' is unique and only to be used once. The advantage is primarily faster execution.
|
|
%% @end
|
|
-spec onetime_auth(Msg, Key) -> Authenticator
|
|
when
|
|
Msg :: binary(),
|
|
Key :: binary(),
|
|
Authenticator :: binary().
|
|
onetime_auth(Msg, Key) -> enacl_nif:crypto_onetimeauth(Msg, Key).
|
|
|
|
%% @doc onetime_auth_verify/3 verifies an ONE-TIME authenticator for a message
|
|
%% Given an `Authenticator', a `Msg' and a `Key'; verify that the MAC for the pair `{Msg, Key}' is really `Authenticator'. Returns
|
|
%% the value `true' if the verification passes. Upon failure, the function returns `false'. Note the caveat from {@link onetime_auth/2}
|
|
%% applies: you are not allowed to ever use the same key again for another message.
|
|
%% @end
|
|
-spec onetime_auth_verify(Authenticator, Msg, Key) -> boolean()
|
|
when
|
|
Authenticator :: binary(),
|
|
Msg :: binary(),
|
|
Key :: binary().
|
|
onetime_auth_verify(A, M, K) -> enacl_nif:crypto_onetimeauth_verify(A, M, K).
|
|
|
|
%% @doc onetime_auth_size/0 returns the number of bytes of the one-time authenticator
|
|
%% @end
|
|
-spec onetime_auth_size() -> pos_integer().
|
|
onetime_auth_size() -> enacl_nif:crypto_onetimeauth_BYTES().
|
|
|
|
%% @doc onetime_auth_key_size/0 returns the byte-size of the onetime authentication key
|
|
%% @end
|
|
-spec onetime_auth_key_size() -> pos_integer().
|
|
onetime_auth_key_size() -> enacl_nif:crypto_onetimeauth_KEYBYTES().
|
|
|
|
%% Helpers
|
|
p_zerobytes() ->
|
|
binary:copy(<<0>>, enacl_nif:crypto_box_ZEROBYTES()).
|
|
|
|
p_box_zerobytes() ->
|
|
binary:copy(<<0>>, enacl_nif:crypto_box_BOXZEROBYTES()).
|
|
|
|
s_zerobytes() ->
|
|
binary:copy(<<0>>, enacl_nif:crypto_secretbox_ZEROBYTES()).
|
|
|
|
s_box_zerobytes() ->
|
|
binary:copy(<<0>>, enacl_nif:crypto_secretbox_BOXZEROBYTES()).
|