Skip to content

Commit c0075aa

Browse files
committed
Add additional docs and examples. Expose Salsa20 calls.
1 parent 9e52984 commit c0075aa

File tree

6 files changed

+286
-102
lines changed

6 files changed

+286
-102
lines changed

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The package can be installed by adding `salchicha` to your list of dependencies
1212
```elixir
1313
def deps do
1414
[
15-
{:salchicha, "~> 0.1.0"}
15+
{:salchicha, "~> 0.3"}
1616
]
1717
end
1818
```

lib/salchicha.ex

+110-15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
defmodule Salchicha do
22
@moduledoc """
3-
A pure-ish Elixir cryptography tool for the Salsa20 and ChaCha20 ciphers.
3+
A pure-ish Elixir cryptography tool for the Salsa20 and ChaCha20 stream ciphers.
44
55
This library has a handful of crypto functions that are compatible with NaCl/libsodium
66
for encryption and decryption with shared secret keys.
@@ -11,17 +11,81 @@ defmodule Salchicha do
1111
1212
The ChaCha20_Poly1305 AEAD cipher is already supported by the `:crypto` module, but XChaCha20
1313
is not. The HChaCha20 hash function, an intermediate step for generating an XChaCha20 sub-key,
14-
is implemented in Elixir so `:crypto` can be leveraged for XChaCha20_Poly1305.
14+
is implemented in Elixir so `:crypto.crypto_one_time_aead/7` can be leveraged for XChaCha20_Poly1305.
1515
1616
The ChaCha20/XChaCha20 ciphers do also have pure Elixir implementations just like Salsa20/XSalsa20,
1717
but unless you are concerned with long-running NIFs blocking schedulers, you should prefer to use
18-
the versions that fully leverage `:crypto` NIFs, the behaviour of applicable functions in this module.
19-
If you wish to you use the elixir implementations, you can call them directly with the functions
18+
the versions that fully leverage `:crypto` NIFs, which is the behavior of functions in this module.
19+
If you wish to use the elixir implementations, you can call them directly with the functions
2020
available in `Salchicha.Chacha` ending in `_pure`.
2121
2222
While this module contains everything you'll need to encrypt and decrypt with XSalsa20_Poly1305
23-
and XChaCha20_Poly1305 authenticated ciphers, the internal modules `Salchicha.Salsa` and
24-
`Salchicha.Chacha` are documented for educational purposes and expose a few primitives.
23+
and XChaCha20_Poly1305, the internal modules `Salchicha.Salsa` and `Salchicha.Chacha` expose a
24+
few additional functions including some primitives and non-extended Salsa20 and ChaCha20 ciphers.
25+
26+
## Examples
27+
28+
Assume we have a key, and an extended nonce, and a plaintext message
29+
```elixir
30+
key = Salchicha.generate_secret_key()
31+
nonce = Salchicha.generate_nonce()
32+
message = "Hello, World!"
33+
```
34+
35+
### XSalsa20 Poly1305 via `secretbox/3` and `secretbox_open/3`
36+
37+
```elixir
38+
encrypted_message =
39+
message
40+
|> Salchicha.secretbox(nonce, key)
41+
|> IO.iodata_to_binary()
42+
# <<211, 79, 12, ...>>
43+
44+
decrypted_message =
45+
encrypted_message
46+
|> Salchicha.secretbox_open(nonce, key)
47+
|> IO.iodata_to_binary()
48+
# "Hello, World!"
49+
```
50+
51+
The secretbox'd message is in the format `| --- 16-byte tag --- | --- cipher text --- |`
52+
53+
Note the `IO.iodata_to_binary/1` calls are optional. The input messages can be `t:iodata/0`.
54+
55+
### XChaCha20 Poly1305 in combined mode
56+
57+
```elixir
58+
encrypted_message =
59+
message
60+
|> Salchicha.xchacha20_poly1305_encrypt(nonce, key, _aad = "XCHACHA")
61+
|> IO.iodata_to_binary()
62+
# <<82, 26, 161, ...>>
63+
64+
decrypted_message =
65+
encrypted_message
66+
|> Salchicha.xchacha20_poly1305_decrypt(nonce, key, _aad = "XCHACHA")
67+
# "Hello, World!"
68+
```
69+
70+
The combined mode encrypted message is in the format `| --- cipher text --- | --- 16-byte tag --- |`
71+
72+
The AAD is optional and will default to `<<>>`, a zero-length binary.
73+
74+
### XChaCha20 Poly1305 in detached mode
75+
76+
```elixir
77+
{cipher_text, tag} =
78+
message
79+
|> Salchicha.xchacha20_poly1305_encrypt_detached(nonce, key, _aad = "XCHACHA")
80+
# {<<82, 26, 161, ...>>, <<1, 199, 251, ...>>}
81+
82+
decrypted_message =
83+
cipher_text
84+
|> Salchicha.xchacha20_poly1305_decrypt_detached(nonce, key, _aad = "XCHACHA", tag)
85+
# "Hello, World!"
86+
```
87+
88+
Detached mode means the cipher text and tag are returned separately instead of being concatenated together.
2589
"""
2690

2791
alias Salchicha.Chacha
@@ -50,10 +114,30 @@ defmodule Salchicha do
50114
32-byte shared secret key used by all variations of Salsa/ChaCha
51115
"""
52116
@type secret_key() :: <<_::256>>
117+
118+
@typedoc """
119+
Plaintext message to encrypt
120+
"""
53121
@type message() :: iodata()
122+
123+
@typedoc """
124+
Encrypted message to decrypt; `t:cipher_text/0` appended or prepended with `t:tag/0`
125+
"""
54126
@type encrypted_message() :: iodata()
127+
128+
@typedoc """
129+
Additional authenticated data
130+
"""
55131
@type aad() :: iodata()
132+
133+
@typedoc """
134+
Tag or MAC (message authentication code)
135+
"""
56136
@type tag() :: <<_::128>>
137+
138+
@typedoc """
139+
Encrypted plaintext
140+
"""
57141
@type cipher_text() :: binary()
58142

59143
@doc """
@@ -69,10 +153,11 @@ defmodule Salchicha do
69153
The return value is the cipher text *prepended* by the 16-byte tag (MAC), compatible with NaCl.
70154
71155
Returns an `t:iolist/0` to reduce binary copies. Call `IO.iodata_to_binary/1` if you need a single binary.
156+
157+
_Calls `Salchicha.Salsa.xsalsa20_poly1305_encrypt/3` then concatenates the tag and cipher text_
72158
"""
73159
@spec secretbox(message(), extended_nonce(), secret_key()) :: iolist()
74160
def secretbox(message, nonce, key) do
75-
message = IO.iodata_to_binary(message)
76161
{cipher_text, tag} = Salsa.xsalsa20_poly1305_encrypt(message, nonce, key)
77162
[tag, cipher_text]
78163
end
@@ -83,18 +168,20 @@ defmodule Salchicha do
83168
This function behaves like `crypto_secretbox_open()` does in NaCl.
84169
85170
## Parameters
86-
- `cipher_message` - The encrypted message (tag *prepended* to cipher text)
171+
- `message` - The encrypted message (tag *prepended* to cipher text)
87172
- `nonce` - 24-byte extended nonce
88173
- `key` - 32-byte secret key
89174
90175
The return value is the decrypted plaintext (as an iolist) or `:error` if authentication failed.
91176
92177
Returns an `t:iolist/0` to reduce binary copies. Call `IO.iodata_to_binary/1` if you need the message as a binary.
178+
179+
_Splits tag and cipher text then calls `Salchicha.Salsa.xsalsa20_poly1305_decrypt/4`_
93180
"""
94181
@spec secretbox_open(encrypted_message(), extended_nonce(), secret_key()) :: iolist() | :error
95-
def secretbox_open(cipher_message, nonce, key) do
96-
cipher_message = IO.iodata_to_binary(cipher_message)
97-
Salsa.xsalsa20_poly1305_decrypt(cipher_message, nonce, key)
182+
def secretbox_open(message, nonce, key) do
183+
<<tag::bytes-16, cipher_text::binary>> = IO.iodata_to_binary(message)
184+
Salsa.xsalsa20_poly1305_decrypt(cipher_text, nonce, key, tag)
98185
end
99186

100187
@doc """
@@ -111,6 +198,8 @@ defmodule Salchicha do
111198
The return value is the cipher text *appended* by the 16-byte tag (MAC), i.e. "combined mode".
112199
113200
Returns an `t:iolist/0` to reduce binary copies. Call `IO.iodata_to_binary/1` if you need a single binary.
201+
202+
_Calls `Salchicha.Chacha.xchacha20_poly1305_encrypt/4` then concatenates the cipher text and tag_
114203
"""
115204
@spec xchacha20_poly1305_encrypt(message(), extended_nonce(), secret_key(), aad()) :: iolist()
116205
def xchacha20_poly1305_encrypt(message, nonce, key, aad \\ <<>>) do
@@ -130,13 +219,15 @@ defmodule Salchicha do
130219
- `aad` - Additional authenticated data (defaults to `<<>>` i.e. no AAD)
131220
132221
The return value is the decrypted plaintext as a binary or `:error` if authentication failed.
222+
223+
_Splits cipher text and tag then calls `Salchicha.Chacha.xchacha20_poly1305_decrypt/5`_
133224
"""
134225
@spec xchacha20_poly1305_decrypt(encrypted_message(), extended_nonce(), secret_key(), aad()) ::
135226
binary() | :error
136-
def xchacha20_poly1305_decrypt(cipher_message, nonce, key, aad \\ <<>>) do
137-
cipher_message = IO.iodata_to_binary(cipher_message)
138-
cipher_text_length = byte_size(cipher_message) - @tag_size
139-
<<cipher_text::bytes-size(cipher_text_length), tag::bytes-size(@tag_size)>> = cipher_message
227+
def xchacha20_poly1305_decrypt(message, nonce, key, aad \\ <<>>) do
228+
message = IO.iodata_to_binary(message)
229+
cipher_text_length = byte_size(message) - @tag_size
230+
<<cipher_text::bytes-size(cipher_text_length), tag::bytes-size(@tag_size)>> = message
140231
Chacha.xchacha20_poly1305_decrypt(cipher_text, nonce, key, aad, tag)
141232
end
142233

@@ -151,6 +242,8 @@ defmodule Salchicha do
151242
This "detached mode" function differs from the "combined mode" `xchacha20_poly1305_encrypt/4`
152243
by returning the tag and cipher text separately in a tuple in the form `{cipher_text, tag}`.
153244
Both `cipher_text` and `tag` will already be binaries.
245+
246+
_Calls `Salchicha.Chacha.xchacha20_poly1305_encrypt/4`_
154247
"""
155248
@spec xchacha20_poly1305_encrypt_detached(message(), extended_nonce(), secret_key(), aad()) ::
156249
{cipher_text(), tag()}
@@ -176,6 +269,8 @@ defmodule Salchicha do
176269
177270
This "detached mode" function differs from the "combined mode" `xchacha20_poly1305_decrypt/4` in that
178271
the cipher text and tag are supplied as separate parameters, not combined as a single message.
272+
273+
_Calls `Salchicha.Chacha.xchacha20_poly1305_decrypt/5`_
179274
"""
180275
@spec xchacha20_poly1305_decrypt_detached(
181276
encrypted_message(),

0 commit comments

Comments
 (0)