1
1
defmodule Salchicha do
2
2
@ 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.
4
4
5
5
This library has a handful of crypto functions that are compatible with NaCl/libsodium
6
6
for encryption and decryption with shared secret keys.
@@ -11,17 +11,81 @@ defmodule Salchicha do
11
11
12
12
The ChaCha20_Poly1305 AEAD cipher is already supported by the `:crypto` module, but XChaCha20
13
13
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.
15
15
16
16
The ChaCha20/XChaCha20 ciphers do also have pure Elixir implementations just like Salsa20/XSalsa20,
17
17
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
20
20
available in `Salchicha.Chacha` ending in `_pure`.
21
21
22
22
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.
25
89
"""
26
90
27
91
alias Salchicha.Chacha
@@ -50,10 +114,30 @@ defmodule Salchicha do
50
114
32-byte shared secret key used by all variations of Salsa/ChaCha
51
115
"""
52
116
@ type secret_key ( ) :: << _ :: 256 >>
117
+
118
+ @ typedoc """
119
+ Plaintext message to encrypt
120
+ """
53
121
@ type message ( ) :: iodata ( )
122
+
123
+ @ typedoc """
124
+ Encrypted message to decrypt; `t:cipher_text/0` appended or prepended with `t:tag/0`
125
+ """
54
126
@ type encrypted_message ( ) :: iodata ( )
127
+
128
+ @ typedoc """
129
+ Additional authenticated data
130
+ """
55
131
@ type aad ( ) :: iodata ( )
132
+
133
+ @ typedoc """
134
+ Tag or MAC (message authentication code)
135
+ """
56
136
@ type tag ( ) :: << _ :: 128 >>
137
+
138
+ @ typedoc """
139
+ Encrypted plaintext
140
+ """
57
141
@ type cipher_text ( ) :: binary ( )
58
142
59
143
@ doc """
@@ -69,10 +153,11 @@ defmodule Salchicha do
69
153
The return value is the cipher text *prepended* by the 16-byte tag (MAC), compatible with NaCl.
70
154
71
155
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_
72
158
"""
73
159
@ spec secretbox ( message ( ) , extended_nonce ( ) , secret_key ( ) ) :: iolist ( )
74
160
def secretbox ( message , nonce , key ) do
75
- message = IO . iodata_to_binary ( message )
76
161
{ cipher_text , tag } = Salsa . xsalsa20_poly1305_encrypt ( message , nonce , key )
77
162
[ tag , cipher_text ]
78
163
end
@@ -83,18 +168,20 @@ defmodule Salchicha do
83
168
This function behaves like `crypto_secretbox_open()` does in NaCl.
84
169
85
170
## Parameters
86
- - `cipher_message ` - The encrypted message (tag *prepended* to cipher text)
171
+ - `message ` - The encrypted message (tag *prepended* to cipher text)
87
172
- `nonce` - 24-byte extended nonce
88
173
- `key` - 32-byte secret key
89
174
90
175
The return value is the decrypted plaintext (as an iolist) or `:error` if authentication failed.
91
176
92
177
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`_
93
180
"""
94
181
@ 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 )
98
185
end
99
186
100
187
@ doc """
@@ -111,6 +198,8 @@ defmodule Salchicha do
111
198
The return value is the cipher text *appended* by the 16-byte tag (MAC), i.e. "combined mode".
112
199
113
200
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_
114
203
"""
115
204
@ spec xchacha20_poly1305_encrypt ( message ( ) , extended_nonce ( ) , secret_key ( ) , aad ( ) ) :: iolist ( )
116
205
def xchacha20_poly1305_encrypt ( message , nonce , key , aad \\ << >> ) do
@@ -130,13 +219,15 @@ defmodule Salchicha do
130
219
- `aad` - Additional authenticated data (defaults to `<<>>` i.e. no AAD)
131
220
132
221
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`_
133
224
"""
134
225
@ spec xchacha20_poly1305_decrypt ( encrypted_message ( ) , extended_nonce ( ) , secret_key ( ) , aad ( ) ) ::
135
226
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
140
231
Chacha . xchacha20_poly1305_decrypt ( cipher_text , nonce , key , aad , tag )
141
232
end
142
233
@@ -151,6 +242,8 @@ defmodule Salchicha do
151
242
This "detached mode" function differs from the "combined mode" `xchacha20_poly1305_encrypt/4`
152
243
by returning the tag and cipher text separately in a tuple in the form `{cipher_text, tag}`.
153
244
Both `cipher_text` and `tag` will already be binaries.
245
+
246
+ _Calls `Salchicha.Chacha.xchacha20_poly1305_encrypt/4`_
154
247
"""
155
248
@ spec xchacha20_poly1305_encrypt_detached ( message ( ) , extended_nonce ( ) , secret_key ( ) , aad ( ) ) ::
156
249
{ cipher_text ( ) , tag ( ) }
@@ -176,6 +269,8 @@ defmodule Salchicha do
176
269
177
270
This "detached mode" function differs from the "combined mode" `xchacha20_poly1305_decrypt/4` in that
178
271
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`_
179
274
"""
180
275
@ spec xchacha20_poly1305_decrypt_detached (
181
276
encrypted_message ( ) ,
0 commit comments