UDP-WG Implementation
Loading...
Searching...
No Matches
crypto.h
1#pragma once
2
3
4#include <openssl/pem.h> // For OpenSSL (HASH,MAC, HMAC)
5#include <sodium.h> // For libsodium (DH, DH_GENERATE, (X)ENCRYPT, (X)DECRYPT)
6#include <stdexcept> // Exceptions
7#include <cstring> // For std::string
8
9
42namespace crypto {
43
44
63 class string {
64 private:
65
66
67 // We just use a vector under the hood, because they're wicked fast and manage memory for us.
68 std::vector<unsigned char> array;
69
70
71 public:
72
73
81 string(const size_t& l = 0) {for (size_t x = 0; x < l; ++x) array.emplace_back(0);}
82
83
88 string(const std::string& string) {
89 for (size_t x = 0; x < string.length(); ++x) array.emplace_back(unsigned(string[x]));
90 }
91
92
100 string(const unsigned char* string, const size_t& l) {
101 for (size_t x = 0; x < l; ++x) array.emplace_back(string[x]);
102 }
103
104
113 string(const char* string) {
114 for (size_t x = 0; x < strlen(string); ++x) array.emplace_back(string[x]);
115 }
116
117
150 ~string() {for (auto& x : array) x = '\0';}
151
152
160 string substr(const size_t& start, const size_t& count = std::string::npos) const {
161
162 // These one-line if statements might seem a little confusing, but the general structure is:
163 // CONDITION ? IF TRUE : IF FALSE
164 // So, here we're setting the value ret to either two values, depending on whether
165 // count == std::string::npos (Which just means the end of the string, std::string
166 // uses it when find() doesn't return anything, for example)
167 // If it is, we obviously don't want to make the string SIZE_MAX bytes long,
168 // so we instead just set it to the size of the array - where we want to start from.
169 // Otherwise, we use count as you expect, setting ret to how many characters we
170 // want to draw from.
171 string ret = count == std::string::npos ? array.size() - start : count;
172 for (size_t x = 0; x < count && x + start < array.size(); ++x) {
173 ret.bytes()[x] = array[x + start];
174 }
175 return ret;
176 }
177
178
184 void resize(const size_t& n, const unsigned char& v=0) {array.resize(n, v);}
185
186
191 void append(const string& s) {
192 for (const auto& x : s.array) array.emplace_back(x);
193 }
194
195
200 std::string str() {
201 std::stringstream out;
202 for (const auto& byte : array) out << std::hex << int(byte);
203 return out.str();
204 }
205
206
211 const size_t length() const {return array.size();}
212
213
220 unsigned char* bytes() {return &array[0];}
221
222
237 const unsigned char* bytes() const {return &array[0];}
238
239
244 std::string to() const {return std::string(reinterpret_cast<const char*>(bytes()), array.size());}
245
246
253 unsigned char& operator [](const size_t& pos) {
254 if (pos >= array.size())
255 throw std::out_of_range("Index into crypto::string out of range!");
256 return array[pos];
257 }
258
259
265 const bool operator == (const string& cmp) const {return array == cmp.array;}
266
267
273 string operator + (const string& a) const {string ret = *this; ret.append(a); return ret;}
274 };
275
276
284 class keypair {
285 private:
286
287 // The private and public keys.
288 string P, p;
289
290 public:
291
292 keypair() = default;
293 keypair(const string& private_key, const string& public_key) : P(private_key), p(public_key) {}
294
295 // Return the private key.
296 string& priv() {return P;}
297 const string& priv() const {return P;}
298
299 // Return the public key.
300 string& pub() {return p;}
301 const string& pub() const {return p;}
302
303 // Return the first key
304 string& first() {return P;}
305 const string& first() const {return P;}
306
307 // Return the second.
308 string& second() {return p;}
309 const string& second() const {return p;}
310 };
311
312
342 string DH(const string& priv, const string& pub) {
343 string product = 32;
344 if (crypto_scalarmult(product.bytes(), priv.bytes(), pub.bytes()) != 0)
345 throw std::runtime_error("Failed to multiply keys!");
346 return product;
347 }
348
349
389
390 // Hold our two 32 byte keys.
391 string pub = 32, priv = 32;
392
393 // Randomly generate a private key, get the public point on the curve.
394 randombytes_buf(priv.bytes(), priv.length());
395 crypto_scalarmult_base(pub.bytes(), priv.bytes());
396
397 // Return
398 return {priv, pub};
399 }
400
401
415 string IV(uint64_t counter) {
416
417 // 32 bits (4 bytes) of zeros + 64 bits (8 bytes) of the counter = 96 bits (12 bytes).
418 string iv = 12;
419
420 // Extract the highest byte of the counter, shifting down to the last.
421 for (size_t x = 0; x < 8; ++x) {
422 iv[4 + x] = counter >> 56;
423 counter << 8;
424 }
425 return iv;
426 }
427
428
450 string ENCRYPT(string key, const uint64_t& counter, const string& plain, const string& data) {
451 // Ensure the key is of the correct size.
452 key.resize(crypto_aead_chacha20poly1305_ietf_KEYBYTES);
453
454 // Initialize the max possible size that the encryption may take
455 string cipher = plain.length() + crypto_aead_chacha20poly1305_ietf_ABYTES;
456 long long unsigned int length = cipher.length();
457
458 // Generate the Nonce.
459 string nonce = IV(counter);
460
461 // Encrypt.
462 crypto_aead_chacha20poly1305_ietf_encrypt(
463 cipher.bytes(), &length,
464 plain.bytes(), plain.length(),
465 data.bytes(), data.length(),
466 nullptr,
467 nonce.bytes(), key.bytes()
468 );
469
470 // Shrink based on the amount of bytes that were actually written.
471 cipher.resize(length);
472 return cipher;
473 }
474
475
486 string DECRYPT(string key, const uint64_t& counter, const string& cipher, const string& data) {
487 key.resize(crypto_aead_chacha20poly1305_ietf_KEYBYTES);
488
489 // Reserve the maximum size.
490 string plain = cipher.length() - crypto_aead_chacha20poly1305_ietf_ABYTES;
491 long long unsigned int length = plain.length();
492
493 auto nonce = IV(counter);
494
495 if (crypto_aead_chacha20poly1305_ietf_decrypt(
496 plain.bytes(), &length,
497 nullptr,
498 cipher.bytes(), cipher.length(),
499 data.bytes(), data.length(),
500 nonce.bytes(), key.bytes()) != 0) {
501 throw std::runtime_error("Modified message! Refusing to decrypt!");
502 }
503 plain.resize(length);
504 return plain;
505 }
506
507
521 keypair XENCRYPT(string key, const string& plain, const string& data) {
522
523 // Ensure the key is of the correct size.
524 key.resize(crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
525
526 // Initialize the max possible size that the encryption may take
527 string cipher = plain.length() + crypto_aead_xchacha20poly1305_ietf_ABYTES;
528 long long unsigned int length = cipher.length();
529
530 // Generate a random nonce.
531 string nonce = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
532 randombytes_buf(nonce.bytes(), nonce.length());
533
534 // Encrypt.
535 crypto_aead_xchacha20poly1305_ietf_encrypt(
536 cipher.bytes(), &length,
537 plain.bytes(), plain.length(),
538 data.bytes(), data.length(),
539 nullptr,
540 nonce.bytes(), key.bytes()
541 );
542
543 // Shrink based on the amount of bytes that were actually written.
544 cipher.resize(length);
545 return {cipher, nonce};
546 }
547
548
557 string XDECRYPT(string key, const keypair& pair, const string& data) {
558 key.resize(crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
559
560 const auto& cipher = pair.first();
561 const auto& nonce = pair.second();
562
563 // Reserve the maximum size.
564 string plain = cipher.length() - crypto_aead_xchacha20poly1305_ietf_ABYTES;
565 long long unsigned int length = plain.length();
566
567 if (crypto_aead_xchacha20poly1305_ietf_decrypt(
568 plain.bytes(), &length,
569 nullptr,
570 cipher.bytes(), cipher.length(),
571 data.bytes(), data.length(),
572 nonce.bytes(), key.bytes()) != 0) {
573 throw std::runtime_error("Modified message! Refusing to decrypt!");
574 }
575 plain.resize(length);
576 return plain;
577 }
578
579
605 string HASH(const string& in) {
606
607 // Initialize the context.
608 EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
609 if (mdctx == nullptr) {
610 throw std::runtime_error("Failed to initialize context!");
611 }
612
613 // Hash the message.We enclose the OpenSSL calls into a DIGEST lambda.
614 string hash = 32;
615 auto DIGEST = [&in, &hash, &mdctx]() {
616
617 // Initialize
618 if (EVP_DigestInit_ex(mdctx, EVP_blake2s256(), nullptr) != 1) return -1;
619
620 // Add our input to the digest.
621 if (EVP_DigestUpdate(mdctx, in.bytes(), in.length()) != 1) return -2;
622
623 // Handle any padding leftover.
624 unsigned int length = 32;
625 if (EVP_DigestFinal_ex(mdctx, hash.bytes(), &length) != 1) return -3;
626 if (length != 32) return -3;
627
628 return 0;
629 };
630
631 // Get the result code, free the context
632 auto result = DIGEST();
633 EVP_MD_CTX_free(mdctx);
634
635 // If an error, throw an exception.
636 if (result < 0) {
637 std::string reason = "BLAKE2s256: Failed to Hash: ";
638 switch (result) {
639 case -1: reason += "Failed to initiailze!"; break;
640 case -2: reason += "Failed to digest!"; break;
641 case -3: reason += "Failed to extract hash!"; break;
642 default: reason += "Unknown reason!"; break;
643 }
644 throw std::runtime_error(reason);
645 }
646 return hash;
647 }
648
649
675 string HMAC(const string& key, const string& input, const size_t& size=32) {
676 if (size > 32 || size == 0)
677 throw std::out_of_range("HMAC can only output sizes from 1-32 inclusive!");
678
679 // Initialize the BLAKE2sMAC.
680 EVP_MAC *mac = EVP_MAC_fetch(nullptr, "BLAKE2SMAC", nullptr);
681 if (mac == nullptr) {
682 throw std::runtime_error("Failed to fetch MAC!");
683 }
684
685 // Initiailze the context.
686 EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac);
687 if (ctx == nullptr) {
688 EVP_MAC_free(mac);
689 throw std::runtime_error("Failed to initialize context!");
690 }
691
692 // Generate the hmac into the hmac string.
693 string hmac = 32;
694 auto GENERATE = [&key, &input, &hmac, &ctx, &size]() {
695 if (EVP_MAC_init(ctx, key.bytes(), key.length(), nullptr) != 1) return -1;
696
697 if (EVP_MAC_update(ctx, input.bytes(), input.length()) != 1) return -2;
698
699 size_t length = hmac.length();
700 if (EVP_MAC_final(ctx, hmac.bytes(), &length, hmac.length()) != 1) return -3;
701 if (length != hmac.length()) return -3;
702
703 return 0;
704 };
705
706 auto result = GENERATE();
707 EVP_MAC_CTX_free(ctx);
708 EVP_MAC_free(mac);
709
710 // If an error, throw an exception.
711 if (result < 0) {
712 std::string reason = "BLAKE2s256-HMAC: Failed to Hash: ";
713 switch (result) {
714 case -1: reason += "Failed to initiailze!"; break;
715 case -2: reason += "Failed to digest input!"; break;
716 case -3: reason += "Failed to extract hmac!"; break;
717 default: reason += "Unknown reason!"; break;
718 }
719 throw std::runtime_error(reason);
720 }
721
722 // Truncate.
723 return hmac.substr(0, size);
724 }
725
726
743 string MAC(const string& key, const string& input) {return HMAC(key, input, 16);}
744
745
767 std::vector<string> KDF(const size_t& n, const string& key, const string& input) {
768 std::vector<string> ret;
769
770 // Create the initial generation, and first iteration.
771 string g0 = HMAC(key, input);
772 ret.emplace_back(g0);
773 if (n == 1) return ret;
774
775 // The 0 and 1 generations are the only ones that have unique construction.
776 ret.emplace_back(HMAC(g0, 0x1));
777
778 // Until we've added N items, keep adding new generations.
779 while (ret.size() != n) {
780
781 // Get the prior generation and add our current generation to the end.
782 auto last = ret.back();
783 auto size = ret.size();
784 last.append({reinterpret_cast<const unsigned char*>(&size), sizeof(size)});
785
786 // Generate.
787 ret.emplace_back(HMAC(g0, last));
788 }
789
790 // Return the entire list of generations.
791 return ret;
792 }
793}
A simple private-public keypair.
Definition crypto.h:284
A cryptographically secure string.
Definition crypto.h:63
~string()
Destroy the string.
Definition crypto.h:150
const unsigned char * bytes() const
Return a immutable byte array of the internal values.
Definition crypto.h:237
std::string to() const
Return a std::string representation of the string.
Definition crypto.h:244
string substr(const size_t &start, const size_t &count=std::string::npos) const
Return a subset of the string.
Definition crypto.h:160
void append(const string &s)
Append another string to end of the caller.
Definition crypto.h:191
string(const char *string)
Construct a string from a character array.
Definition crypto.h:113
string(const std::string &string)
Construct a string from a std::string.
Definition crypto.h:88
std::string str()
Return a hexadecimal representation of the crypto::string.
Definition crypto.h:200
void resize(const size_t &n, const unsigned char &v=0)
Resize the string.
Definition crypto.h:184
unsigned char * bytes()
Return a mutable byte array that can be directly manipulated.
Definition crypto.h:220
unsigned char & operator[](const size_t &pos)
Index the string.
Definition crypto.h:253
string(const unsigned char *string, const size_t &l)
Construct a string from a character array and size.
Definition crypto.h:100
string operator+(const string &a) const
Concatonation operator.
Definition crypto.h:273
const bool operator==(const string &cmp) const
Equivalence operator.
Definition crypto.h:265
const size_t length() const
Return the length of the string.
Definition crypto.h:211
string(const size_t &l=0)
Construct a string with a fixed, zeroed size.
Definition crypto.h:81
The cryptographic implementations for WireGuard.
Definition crypto.h:42
string XDECRYPT(string key, const keypair &pair, const string &data)
Decrypt with XChaCha20-Poly1305.
Definition crypto.h:557
std::vector< string > KDF(const size_t &n, const string &key, const string &input)
Perform the HKDF scheme on our HMAC function.
Definition crypto.h:767
string DH(const string &priv, const string &pub)
Definition crypto.h:342
string HMAC(const string &key, const string &input, const size_t &size=32)
Compute an HMAC using BLAKE and a key.
Definition crypto.h:675
keypair DH_GENERATE()
Generate a Curve25519 keypair.
Definition crypto.h:388
string ENCRYPT(string key, const uint64_t &counter, const string &plain, const string &data)
Encrypt with ChaCha20-Poly1305.
Definition crypto.h:450
string HASH(const string &in)
Generate a BLAKE2s256 Hash of the input.
Definition crypto.h:605
keypair XENCRYPT(string key, const string &plain, const string &data)
Encrypt with XChaCha20-Poly1305.
Definition crypto.h:521
string DECRYPT(string key, const uint64_t &counter, const string &cipher, const string &data)
Decrypt with ChaCha20-Poly1305.
Definition crypto.h:486
string IV(uint64_t counter)
Format the IV array given the WireGuard Counter.
Definition crypto.h:415
string MAC(const string &key, const string &input)
OpenSSL makes no difference between HMAC-BLAKE2s256 and Keyed BLAKE2s256 Besides setting the size....
Definition crypto.h:743