1 module dcrypt.nacl.box;
2
3 /// High level API for asymmetric authenticated encryption.
4 /// Compatible to http://nacl.cr.yp.to/box.html.
5
6 public import dcrypt.nacl.secretbox;
7 import dcrypt.ecc.curve25519;
8 import dcrypt.streamcipher.salsa;
9 import dcrypt.util;
10 import dcrypt.exceptions: InvalidCipherTextException;
11
12 /// Encrypt a message using asymmetric cryptography.
13 ///
14 /// Params:
15 /// msg = The secret message.
16 /// nonce = A number unique per (secret_key, public_key) pair.
17 /// secret_key = Your secret key.
18 /// public_key = Recipients public key.
19 ///
20 /// Returns: Encrypted and authenticated packet.
21 public ubyte[] box(in ubyte[] msg, in ubyte[] nonce, in ubyte[] secret_key, in ubyte[] public_key) @safe nothrow {
22
23 ubyte[32] shared_key = derive_shared_key(secret_key, public_key);
24 scope(exit) {
25 wipe(shared_key);
26 }
27
28 return secretbox(msg, nonce, shared_key);
29 }
30
31 /// Generate a public key from a secret key.
32 ///
33 /// Params:
34 /// secret_key = A 32 byte secret key, choosen randomly.
35 ///
36 /// Returns: Returns the public key matching to the given secret key.
37 public ubyte[32] box_pubkey(in ubyte[] secret_key) nothrow @safe @nogc
38 in {
39 assert(secret_key.length == 32, "Secret key must be 32 bytes.");
40 } body {
41 return curve25519_scalarmult(secret_key);
42 }
43
44 /// Decrypts a packet generated by `box()`.
45 ///
46 /// Params:
47 /// box = The received packet.
48 /// nonce = A number unique per (secret_key, public_key) pair.
49 /// secret_key = Your secret key.
50 /// public_key = Senders public key.
51 ///
52 /// Returns: Plaintext if authentication tag is valid.
53 ///
54 /// Throws: IllegalCipherTextException if authentication tag is not valid.
55 ubyte[] box_open(in ubyte[] box, in ubyte[] nonce, in ubyte[] secret_key, in ubyte[] public_key) @safe {
56
57 ubyte[32] shared_key = derive_shared_key(secret_key, public_key);
58 scope(exit) {
59 wipe(shared_key);
60 }
61
62 return secretbox_open(box, nonce, shared_key);
63 }
64
65 /// Derive a shared key from a secret and a public key. Used for box() and box_open().
66 ///
67 /// Returns: The shared key.
68 private ubyte[32] derive_shared_key(in ubyte[] secret_key, in ubyte[] public_key) @safe nothrow @nogc {
69 ubyte[32] shared_key;
70
71 immutable ubyte[16] zero_nonce = 0;
72
73 shared_key = curve25519_scalarmult(secret_key, public_key);
74 shared_key = HSalsa(shared_key, zero_nonce);
75
76 return shared_key;
77 }
78
79 /// Test vectors from naclcrypto-20090310.pdf
80 unittest {
81 alias immutable ubyte[] octets;
82
83 octets alice_sk = cast(octets) x"
84 77076d0a7318a57d3c16c17251b26645
85 df4c2f87ebc0992ab177fba51db92c2a";
86
87 octets bob_pk = cast(octets) x"
88 de9edb7d7b7dc1b4d35b61c2ece43537
89 3f8343c85b78674dadfc7e146f882b4f";
90
91 octets nonce = cast(octets) x"
92 69696ee955b62b73cd62bda875fc73d6
93 8219e0036b7a0b37";
94
95 octets msg = cast(octets) x"
96 be075fc53c81f2d5cf141316ebeb0c7b
97 5228c52a4c62cbd44b66849b64244ffc
98 e5ecbaaf33bd751a1ac728d45e6c6129
99 6cdc3c01233561f41db66cce314adb31
100 0e3be8250c46f06dceea3a7fa1348057
101 e2f6556ad6b1318a024a838f21af1fde
102 048977eb48f59ffd4924ca1c60902e52
103 f0a089bc76897040e082f93776384864
104 5e0705";
105
106 octets boxed_ref = cast(octets) x"
107 f3ffc7703f9400e52a7dfb4b3d3305d9
108 8e993b9f48681273c29650ba32fc76ce
109 48332ea7164d96a4476fb8c531a1186a
110 c0dfc17c98dce87b4da7f011ec48c972
111 71d2c20f9b928fe2270d6fb863d51738
112 b48eeee314a7cc8ab932164548e526ae
113 90224368517acfeabd6bb3732bc0e9da
114 99832b61ca01b6de56244a9e88d5f9b3
115 7973f622a43d14a6599b1f654cb45a74
116 e355a5";
117
118 test_box(msg, boxed_ref, nonce, alice_sk, bob_pk);
119 }
120
121 version(unittest) {
122 /// Helper function for testing.
123 /// Params:
124 /// msg = Plaintext.
125 /// boxed_ref = Expected ciphertext with authentication tag.
126 /// nonce = A number unique per (sk, pk) pair.
127 /// sk = Own secret key.
128 /// pk = Public key.
129 void test_box(in ubyte[] msg, in ubyte[] boxed_ref, in ubyte[] nonce, in ubyte[] sk, in ubyte[] pk) {
130 // test encryption
131 ubyte[] boxed = box(msg, nonce, sk, pk);
132 if(boxed_ref !is null) {
133 assert(boxed == boxed_ref, "box() failed");
134 }
135
136 // test decryption
137 if(boxed_ref !is null) {
138 ubyte[] unboxed = box_open(boxed_ref, nonce, sk, pk);
139 assert(unboxed == msg, "box_open failed");
140 } else {
141 ubyte[] unboxed = box_open(boxed, nonce, sk, pk);
142 assert(unboxed == msg, "box_open failed");
143 }
144
145 // test invalid authentication
146 ubyte[] tampered_box = boxed.dup;
147 tampered_box[$-1] ^= 1;
148
149 bool exception = false;
150 try {
151 ubyte[] unboxed = box_open(tampered_box, nonce, sk, pk);
152 assert(false, "Invalid ciphertext passed as valid.");
153 } catch(InvalidCipherTextException e) {
154 exception = true;
155 } finally {
156 assert(exception, "Expected exception has not been thrown.");
157 }
158 }
159 }