Commit 50b00583 authored by Jesper Louis Andersen's avatar Jesper Louis Andersen
Browse files

Improve the documentation of the project.

Go over the README file, and improve its contents. Add a Usage/hints section
with some helpful hints on how to use the library in turn. Also while here, make
sure that `rebar doc` works as expected and fix every documentation bug in
the EDoc sections, so it compiles and works. Verify the documentation output
is nice-looking while at it.
parent 2a23a16e
......@@ -20,14 +20,37 @@ or
* Complete NaCl library, implementing all default functionality.
* Implements a small set of additional functionality from libsodium. Most notably access to a proper CSPRNG random source
* Tests created by aggressive use of Erlang QuickCheck.
* NaCl is a very fast cryptographic library. That is, crypto-operations runs quickly on modern CPUs, with ample security margins. This makes it highly useful on the server-side, where simultaneous concurrent load on the system means encryption can have a considerable overhead.
This package draws heavy inspiration from "erlang-nacl" by Tony Garnock-Jones.
This package draws heavy inspiration from "erlang-nacl" by Tony Garnock-Jones, and started its life with a gently nod in that direction. However, it is a rewrite and it alters lots of code from Tony's original work.
In addition, I would like to thank Steve Vinoski, Rickard Green, and Sverker Eriksson for providing the Dirty Scheduler API in the first place.
# USING:
In general, consult the NaCl documentation at
http://nacl.cr.yp.to
but also note that our interface has full Edoc documentation, generated by executing
rebar doc
## Hints
In general, the primitives provided by NaCl are intermediate-level primitives. Rather than you having to select a cipher suite, it is selected for you, and primitives are provided at a higher level. However, their correct use is still needed in order to be secure:
* Always make sure you obey the scheme of *nonce* values. If you ever reuse a nonce, and an attacker figures this out, the system will leak the XOR difference of messages sent with the same nonce. Given enough guessing, this can in turn leak the encryption stream of bits and every message hereafter, sent on the same keypair combination and reusing that nonce, will be trivially breakable.
* Use the beforenm/afternm primitives if using the `box` public-key encryption scheme. Precomputing the Curve25519 operations yields much faster operation in practice for a stream. Consult the `enacl_timing:all/0` function in order to see how much faster it is for your system. The authors Core i7-4900MQ can process roughly 32 Kilobyte data on the stream in the time it takes to do the Curve25519 computations. While NaCl is *fast*, this can make it even faster in practice.
* Encrypting very large blocks of data, several megabytes for instance, is problematic for two reasons. First, while the library attempts to avoid being a memory hog, you need at least a from-space and a to-space for the data, meaning you need at least double the memory for the operation. Furthermore, while such large blocks are executed on the dirty schedulers, they will never yield the DS for another piece of work. This means you end up blocking the dirty schedulers in turn. It is often better to build a framing scheme and encrypt data in smaller chunks, say 64 or 128 kilobytes at a time. In any case, it is important to measure. Especially for latency.
* The library should provide correct success type specifications. This means you can use the dialyzer on your code and get hints for incorrect usage of the library.
* Note that every "large" input to the library accepts `iodata()` rather than `binary()` data. The library itself will convert `iodata()` to binaries internally, so you don't have to do it at your end. It often yields simpler code since you can just build up an iolist of your data and shove it to the library. Key material, nonces and the like are generally *not* accepted as `iodata()` however but requires you to input binary data. This is a deliberate choice since most such material is not supposed to be broken up and constructed ever (except perhaps for the Nonce construction).
* The `enacl:randombytes/1` function provides portable access to the CSPRNG of your kernel. It is an *excellent* source of CSPRNG random data. If you need PRNG data with a seed for testing purposes, use the `random` module of Erlang or Kenji Rikitake's `sfmt` bindings instead. But do note these do not provide cryptographically secure random numbers. The other alternative is the `crypto` module, which are bindings to OpenSSL with all its blessings and/or curses.
* Beware of timing attacks against your code! A typical area is string comparison, where the comparator function exits early. In that case, an attacker can time the response in order to guess at how many bytes where matched. This in turn enables some attacks where you use a foreign system as an oracle in order to learn the structure of a string, breaking the cryptograhic system in the process.
# TODO
* Write simple correctness unit tests for the different NaCl primitives.
* Write simple correctness unit tests for the different NaCl primitives. This will verify the integrity towards the underlying NaCl system.
# Versions
......
......@@ -11,7 +11,7 @@
%%% <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
%%% <p><b>Note:</b> All functions will fail with a `badarg' error if given incorrect
%%% parameters.</p>
%%% @end.
-module(enacl).
......@@ -101,8 +101,12 @@
-define(RANDOMBYTES_SIZE, 1024).
-define(RANDOMBYTES_REDUCTIONS, 200).
%% @doc reds/1 counts the number of reductions and scheduler yields for a thunk
%%
%% Count reductions and number of scheduler yields for Fun. Fun is assumed
%% to be one of the above exor variants.
%% @end
-spec reds(fun (() -> any())) -> #{ atom() => any() }.
reds(Fun) ->
Parent = self(),
Pid = spawn(fun() ->
......@@ -112,7 +116,8 @@ reds(Fun) ->
Fun(),
R1 = process_info(Self, reductions),
T = timer:now_diff(os:timestamp(), Start),
Parent ! {Self,{T, R1, R0}} end),
Parent ! {Self,#{ time_diff => T, after_reductions => R1, before_reductions => R0}}
end),
receive
{Pid,Result} ->
Result
......@@ -122,6 +127,7 @@ reds(Fun) ->
%% -----------------
%% @doc hash/1 hashes data into a cryptographically secure checksum.
%%
%% <p>Given an iodata(), `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>
......@@ -140,6 +146,7 @@ hash(Bin) ->
end.
%% @doc verify_16/2 implements constant time 16-byte binary() 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
......@@ -154,6 +161,7 @@ verify_16(X, Y) when is_binary(X), is_binary(Y) -> enacl_nif:crypto_verify_16(X,
verify_16(_, _) -> error(badarg).
%% @doc verify_32/2 implements constant time 32-byte iolist() verification
%%
%% This function works as {@link verify_16/2} but does so on 32 byte strings. Same caveats apply.
%% @end
-spec verify_32(binary(), binary()) -> boolean().
......@@ -163,6 +171,7 @@ verify_32(_, _) -> error(badarg).
%% 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.
......@@ -172,8 +181,9 @@ 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.
%%
%% 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 :: iodata(),
......@@ -190,9 +200,11 @@ box(Msg, Nonce, PK, SK) ->
end.
%% @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
%%
%% 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.
%% @end
-spec box_open(CipherText, Nonce, PK, SK) -> {ok, Msg} | {error, failed_verification}
when CipherText :: iodata(),
Nonce :: binary(),
......@@ -227,6 +239,7 @@ box_beforenm(PK, SK) ->
R.
%% @doc box_afternm/3 works like `box/4' but uses a precomputed key
%%
%% Calling `box_afternm(M, Nonce, K)' for a precomputed key `K = box_beforenm(PK, SK)' works exactly as
%% if you had called `box(M, Nonce, PK, SK)'. Except that it avoids computations in the elliptic curve Curve25519,
%% and thus is a much faster operation.
......@@ -247,6 +260,7 @@ box_afternm(Msg, Nonce, Key) ->
end.
%% @doc box_open_afternm/3 works like `box_open/4` but uses a precomputed key
%%
%% Calling `box_open_afternm(M, Nonce, K)' for a precomputed key `K = box_beforenm(PK, SK)' works exactly as
%% if you had called `box_open(M, Nonce, PK, SK)'. Except the operation is much faster as it avoids costly
%% computations in the elliptic curve Curve25519.
......@@ -274,6 +288,7 @@ box_open_afternm(CipherText, Nonce, Key) ->
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().
......@@ -300,6 +315,7 @@ 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() -> #{ atom() => binary() }.
......@@ -308,6 +324,7 @@ 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
......@@ -324,6 +341,7 @@ sign(M, SK) ->
end.
%% @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.
......@@ -354,6 +372,7 @@ box_secret_key_bytes() ->
enacl_nif:crypto_box_SECRETKEYBYTES().
%% @doc secretbox/3 encrypts a message with a key
%%
%% Given a `Msg', a `Nonce' and a `Key' encrypt the message with the Key while taking the
%% nonce into consideration. The function returns the Box obtained from the encryption.
%% @end
......@@ -375,6 +394,7 @@ secretbox(Msg, Nonce, Key) ->
enacl_nif:crypto_secretbox([s_zerobytes(), Msg], Nonce, Key)
end.
%% @doc secretbox_open/3 opens a sealed box.
%%
%% Given a boxed `CipherText' and given we know the used `Nonce' and `Key' we can open the box
%% to obtain the `Msg` within. Returns either `{ok, Msg}' or `{error, failed_verification}'.
%% @end
......@@ -400,9 +420,17 @@ secretbox_open(CipherText, Nonce, Key) ->
end
end.
%% @doc secretbox_nonce_size/0 returns the size of the secretbox nonce
%%
%% When encrypting with a secretbox, the nonce must have this size
%% @end
secretbox_nonce_size() ->
enacl_nif:crypto_secretbox_NONCEBYTES().
%% @doc secretbox_key_size/0 returns the size of the secretbox key
%%
%% When encrypting with a secretbox, the key must have this size
%% @end
secretbox_key_size() ->
enacl_nif:crypto_secretbox_KEYBYTES().
......@@ -417,6 +445,7 @@ stream_nonce_size() -> enacl_nif:crypto_stream_NONCEBYTES().
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>
......@@ -439,6 +468,7 @@ stream(Len, Nonce, Key) when is_integer(Len), Len >= 0 ->
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
......@@ -470,6 +500,7 @@ auth_key_size() -> enacl_nif:crypto_auth_KEYBYTES().
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
......@@ -487,6 +518,7 @@ auth(Msg, Key) ->
end.
%% @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
......@@ -507,6 +539,7 @@ auth_verify(A, M, K) ->
end.
%% @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 noticably faster execution.
%% @end
......@@ -527,6 +560,7 @@ onetime_auth(Msg, Key) ->
end.
%% @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.
......@@ -560,6 +594,7 @@ onetime_auth_key_size() -> enacl_nif:crypto_onetimeauth_KEYBYTES().
%% Obtaining random bytes
%% @doc randombytes/1 produces a stream of random bytes of the given size
%%
%% The security properties of the random stream are that of the libsodium library. Specifically,
%% we use:
%%
......
%%% @doc module enacl_ext implements various enacl extensions.
%%% <p>None of the extensions listed here are part of the official NaCl library.
%%% Things may be removed without further notice if it suddenly ends up being
%%% Functions may be removed without further notice if it suddenly ends up being
%%% better to do something differently than the solution given here.
%%% </p>
-module(enacl_ext).
......
%%% @doc module enacl_timing provides helpers for timing enacl toward your installation
%%% @doc module enacl_timing provides helpers for timing enacl toward your installation.
%%% <p>To use this module, make sure you disable CPU frequency scaling to obtain better numbers for your system.</p>
%%% @end
-module(enacl_timing).
-export([all/0]).
%% @doc all/0 runs all timing code and reports the timings of different enacl primitives
%% <p>Returns a nested list structure containing maps which explains the runtimes of different primitives</p>
%% <p>The structure is subject to change without notice, so don't rely on it in code.</p>
%% @end
all() ->
[time_hashing(),
time_box(),
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment