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 }