diff --git a/c_src/enacl_nif.c b/c_src/enacl_nif.c index 1b154dc..da54551 100644 --- a/c_src/enacl_nif.c +++ b/c_src/enacl_nif.c @@ -641,6 +641,16 @@ ERL_NIF_TERM enif_crypto_secretbox_BOXZEROBYTES(ErlNifEnv *env, int argc, ERL_NI return enif_make_int64(env, crypto_secretbox_BOXZEROBYTES); } +static +ERL_NIF_TERM enif_crypto_stream_chacha20_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_stream_chacha20_KEYBYTES); +} + +static +ERL_NIF_TERM enif_crypto_stream_chacha20_NONCEBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + return enif_make_int64(env, crypto_stream_chacha20_NONCEBYTES); +} + static ERL_NIF_TERM enif_crypto_stream_KEYBYTES(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { return enif_make_int64(env, crypto_stream_KEYBYTES); @@ -756,6 +766,61 @@ ERL_NIF_TERM enif_crypto_secretbox_open(ErlNifEnv *env, int argc, ERL_NIF_TERM c padded_ciphertext.size - crypto_secretbox_ZEROBYTES); } +static +ERL_NIF_TERM enif_crypto_stream_chacha20(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary c, n, k; + ErlNifUInt64 clen; + + if ( + (argc != 3) || + (!enif_get_uint64(env, argv[0], &clen)) || + (!enif_inspect_binary(env, argv[1], &n)) || + (!enif_inspect_binary(env, argv[2], &k))) { + return enif_make_badarg(env); + } + + if ( + (k.size != crypto_stream_chacha20_KEYBYTES) || + (n.size != crypto_stream_chacha20_NONCEBYTES)) { + return enif_make_badarg(env); + } + + if (!enif_alloc_binary(clen, &c)) { + return nacl_error_tuple(env, "alloc_failed"); + } + + crypto_stream_chacha20(c.data, c.size, n.data, k.data); + + return enif_make_binary(env, &c); +} + +static +ERL_NIF_TERM enif_crypto_stream_chacha20_xor(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { + ErlNifBinary c, m, n, k; + + if ( + (argc != 3) || + (!enif_inspect_iolist_as_binary(env, argv[0], &m)) || + (!enif_inspect_binary(env, argv[1], &n)) || + (!enif_inspect_binary(env, argv[2], &k))) { + return enif_make_badarg(env); + } + + if ( + (k.size != crypto_stream_chacha20_KEYBYTES) || + (n.size != crypto_stream_chacha20_NONCEBYTES)) { + return enif_make_badarg(env); + } + + if (!enif_alloc_binary(m.size, &c)) { + return nacl_error_tuple(env, "alloc_failed"); + } + + crypto_stream_chacha20_xor(c.data, m.data, m.size, n.data, k.data); + + return enif_make_binary(env, &c); +} + static ERL_NIF_TERM enif_crypto_stream(ErlNifEnv *env, int argc, ERL_NIF_TERM const argv[]) { ErlNifBinary c, n, k; @@ -1071,6 +1136,13 @@ static ErlNifFunc nif_funcs[] = { {"crypto_secretbox_open_b", 3, enif_crypto_secretbox_open}, {"crypto_secretbox_open", 3, enif_crypto_secretbox_open, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_stream_chacha20_KEYBYTES", 0, enif_crypto_stream_chacha20_KEYBYTES}, + {"crypto_stream_chacha20_NONCEBYTES", 0, enif_crypto_stream_chacha20_NONCEBYTES}, + {"crypto_stream_chacha20_b", 3, enif_crypto_stream_chacha20}, + {"crypto_stream_chacha20", 3, enif_crypto_stream_chacha20, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_stream_chacha20_xor_b", 3, enif_crypto_stream_chacha20_xor}, + {"crypto_stream_chacha20_xor", 3, enif_crypto_stream_chacha20_xor, ERL_NIF_DIRTY_JOB_CPU_BOUND}, + {"crypto_stream_KEYBYTES", 0, enif_crypto_stream_KEYBYTES}, {"crypto_stream_NONCEBYTES", 0, enif_crypto_stream_NONCEBYTES}, {"crypto_stream_b", 3, enif_crypto_stream}, diff --git a/src/enacl.erl b/src/enacl.erl index 0244152..5e06dc8 100644 --- a/src/enacl.erl +++ b/src/enacl.erl @@ -49,6 +49,11 @@ secretbox/3, secretbox_open/3, + stream_chacha20_key_size/0, + stream_chacha20_nonce_size/0, + stream_chacha20/3, + stream_chacha20_xor/3, + stream_key_size/0, stream_nonce_size/0, stream/3, @@ -129,6 +134,8 @@ -define(S_ZEROBYTES, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>). %% 32 bytes -define(CRYPTO_SECRETBOX_BOXZEROBYTES, 16). -define(S_BOXZEROBYTES, <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>>). %% 16 bytes +-define(CRYPTO_STREAM_CHACHA20_KEYBYTES, 32). +-define(CRYPTO_STREAM_CHACHA20_NONCEBYTES, 8). -define(CRYPTO_STREAM_KEYBYTES, 32). -define(CRYPTO_STREAM_NONCEBYTES, 24). @@ -141,6 +148,8 @@ verify() -> ?S_BOXZEROBYTES), Verifiers = [ + {crypto_stream_chacha20_KEYBYTES, ?CRYPTO_STREAM_CHACHA20_KEYBYTES}, + {crypto_stream_chacha20_NONCEBYTES, ?CRYPTO_STREAM_CHACHA20_NONCEBYTES}, {crypto_stream_KEYBYTES, ?CRYPTO_STREAM_KEYBYTES}, {crypto_stream_NONCEBYTES, ?CRYPTO_STREAM_NONCEBYTES}, {crypto_box_ZEROBYTES, ?CRYPTO_BOX_ZEROBYTES}, @@ -501,6 +510,63 @@ secretbox_nonce_size() -> secretbox_key_size() -> enacl_nif:crypto_secretbox_KEYBYTES(). +%% @doc stream_chacha20_nonce_size/0 returns the byte size of the nonce for streams +%% @end +-spec stream_chacha20_nonce_size() -> pos_integer(). +stream_chacha20_nonce_size() -> ?CRYPTO_STREAM_CHACHA20_NONCEBYTES. + +%% @doc stream_key_size/0 returns the byte size of the key for streams +%% @end +-spec stream_chacha20_key_size() -> pos_integer(). +stream_chacha20_key_size() -> ?CRYPTO_STREAM_CHACHA20_KEYBYTES. + +%% @doc stream_chacha20/3 produces a cryptographic stream suitable for secret-key encryption +%% +%%

Given a positive `Len' a `Nonce' and a `Key', the stream_chacha20/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.

+%%

Note: 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.

+%% @end +-spec stream_chacha20(Len, Nonce, Key) -> CryptoStream + when + Len :: non_neg_integer(), + Nonce :: binary(), + Key :: binary(), + CryptoStream :: binary(). +stream_chacha20(Len, Nonce, Key) when is_integer(Len), Len >= 0, Len =< ?STREAM_SIZE -> + bump(enacl_nif:crypto_stream_chacha20_b(Len, Nonce, Key), + ?STREAM_REDUCTIONS, + ?STREAM_SIZE, + Len); +stream_chacha20(Len, Nonce, Key) when is_integer(Len), Len >= 0 -> + enacl_nif:crypto_stream_chacha20(Len, Nonce, Key); +stream_chacha20(_, _, _) -> error(badarg). + +%% @doc stream_chacha20_xor/3 encrypts a plaintext message into ciphertext +%% +%% The stream_chacha20_xor/3 function works by using the {@link stream_chacha20/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_chacha20_xor(Msg, Nonce, Key) -> CipherText + when + Msg :: iodata(), + Nonce :: binary(), + Key :: binary(), + CipherText :: binary(). +stream_chacha20_xor(Msg, Nonce, Key) -> + case iolist_size(Msg) of + K when K =< ?STREAM_SIZE -> + bump(enacl_nif:crypto_stream_chacha20_xor_b(Msg, Nonce, Key), + ?STREAM_REDUCTIONS, + ?STREAM_SIZE, + K); + _ -> + enacl_nif:crypto_stream_chacha20_xor(Msg, Nonce, Key) + end. + +%% @doc auth_key_size/0 returns the byte-size of the authentication key +%% @end %% @doc stream_nonce_size/0 returns the byte size of the nonce for streams %% @end -spec stream_nonce_size() -> pos_integer(). diff --git a/src/enacl_nif.erl b/src/enacl_nif.erl index d18c2c0..36ab1d1 100644 --- a/src/enacl_nif.erl +++ b/src/enacl_nif.erl @@ -52,6 +52,14 @@ crypto_secretbox_open/3, crypto_secretbox_open_b/3, + crypto_stream_chacha20_KEYBYTES/0, + crypto_stream_chacha20_NONCEBYTES/0, + + crypto_stream_chacha20/3, + crypto_stream_chacha20_b/3, + crypto_stream_chacha20_xor/3, + crypto_stream_chacha20_xor_b/3, + crypto_stream_KEYBYTES/0, crypto_stream_NONCEBYTES/0, @@ -168,6 +176,13 @@ crypto_secretbox_b(_Msg, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). crypto_secretbox_open(_Msg, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). crypto_secretbox_open_b(_Msg, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_KEYBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_NONCEBYTES() -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20(_Bytes, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_b(_Bytes, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_xor(_M, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). +crypto_stream_chacha20_xor_b(_M, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded). + crypto_stream_KEYBYTES() -> erlang:nif_error(nif_not_loaded). crypto_stream_NONCEBYTES() -> erlang:nif_error(nif_not_loaded). crypto_stream(_Bytes, _Nonce, _Key) -> erlang:nif_error(nif_not_loaded).