#ifndef SIMPLE_WEB_CRYPTO_HPP #define SIMPLE_WEB_CRYPTO_HPP #include #include #include #include #include #include #include #include #include #include namespace SimpleWeb { // TODO 2017: remove workaround for MSVS 2012 #if _MSC_VER == 1700 // MSVS 2012 has no definition for round() inline double round(double x) noexcept { // Custom definition of round() for positive numbers return floor(x + 0.5); } #endif class Crypto { const static std::size_t buffer_size = 131072; public: class Base64 { public: /// Returns Base64 encoded string from input string. static std::string encode(const std::string &input) noexcept { std::string base64; BIO *bio, *b64; auto bptr = BUF_MEM_new(); b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); bio = BIO_new(BIO_s_mem()); BIO_push(b64, bio); BIO_set_mem_buf(b64, bptr, BIO_CLOSE); // Write directly to base64-buffer to avoid copy auto base64_length = static_cast(round(4 * ceil(static_cast(input.size()) / 3.0))); base64.resize(base64_length); bptr->length = 0; bptr->max = base64_length + 1; bptr->data = &base64[0]; if(BIO_write(b64, &input[0], static_cast(input.size())) <= 0 || BIO_flush(b64) <= 0) base64.clear(); // To keep &base64[0] through BIO_free_all(b64) bptr->length = 0; bptr->max = 0; bptr->data = nullptr; BIO_free_all(b64); return base64; } /// Returns Base64 decoded string from base64 input. static std::string decode(const std::string &base64) noexcept { std::string ascii((6 * base64.size()) / 8, '\0'); // The size is a up to two bytes too large. BIO *b64, *bio; b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); // TODO: Remove in 2022 or later #if(defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER < 0x1000214fL) || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x2080000fL) bio = BIO_new_mem_buf(const_cast(&base64[0]), static_cast(base64.size())); #else bio = BIO_new_mem_buf(&base64[0], static_cast(base64.size())); #endif bio = BIO_push(b64, bio); auto decoded_length = BIO_read(bio, &ascii[0], static_cast(ascii.size())); if(decoded_length > 0) ascii.resize(static_cast(decoded_length)); else ascii.clear(); BIO_free_all(b64); return ascii; } }; /// Returns hex string from bytes in input string. static std::string to_hex_string(const std::string &input) noexcept { std::stringstream hex_stream; hex_stream << std::hex << std::internal << std::setfill('0'); for(auto &byte : input) hex_stream << std::setw(2) << static_cast(static_cast(byte)); return hex_stream.str(); } /// Return hash value using specific EVP_MD from input string. static std::string message_digest(const std::string &str, const EVP_MD *evp_md, std::size_t digest_length) noexcept { std::string md(digest_length, '\0'); auto ctx = EVP_MD_CTX_create(); EVP_MD_CTX_init(ctx); EVP_DigestInit_ex(ctx, evp_md, nullptr); EVP_DigestUpdate(ctx, str.data(), str.size()); EVP_DigestFinal_ex(ctx, reinterpret_cast(&md[0]), nullptr); EVP_MD_CTX_destroy(ctx); return md; } /// Return hash value using specific EVP_MD from input stream. static std::string stream_digest(std::istream &stream, const EVP_MD *evp_md, std::size_t digest_length) noexcept { std::string md(digest_length, '\0'); std::unique_ptr buffer(new char[buffer_size]); std::streamsize read_length; auto ctx = EVP_MD_CTX_create(); EVP_MD_CTX_init(ctx); EVP_DigestInit_ex(ctx, evp_md, nullptr); while((read_length = stream.read(buffer.get(), buffer_size).gcount()) > 0) EVP_DigestUpdate(ctx, buffer.get(), static_cast(read_length)); EVP_DigestFinal_ex(ctx, reinterpret_cast(&md[0]), nullptr); EVP_MD_CTX_destroy(ctx); return md; } /// Returns md5 hash value from input string. static std::string md5(const std::string &input, std::size_t iterations = 1) noexcept { auto evp_md = EVP_md5(); auto hash = message_digest(input, evp_md, MD5_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, MD5_DIGEST_LENGTH); return hash; } /// Returns md5 hash value from input stream. static std::string md5(std::istream &stream, std::size_t iterations = 1) noexcept { auto evp_md = EVP_md5(); auto hash = stream_digest(stream, evp_md, MD5_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, MD5_DIGEST_LENGTH); return hash; } /// Returns sha1 hash value from input string. static std::string sha1(const std::string &input, std::size_t iterations = 1) noexcept { auto evp_md = EVP_sha1(); auto hash = message_digest(input, evp_md, SHA_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, SHA_DIGEST_LENGTH); return hash; } /// Returns sha1 hash value from input stream. static std::string sha1(std::istream &stream, std::size_t iterations = 1) noexcept { auto evp_md = EVP_sha1(); auto hash = stream_digest(stream, evp_md, SHA_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, SHA_DIGEST_LENGTH); return hash; } /// Returns sha256 hash value from input string. static std::string sha256(const std::string &input, std::size_t iterations = 1) noexcept { auto evp_md = EVP_sha256(); auto hash = message_digest(input, evp_md, SHA256_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, SHA256_DIGEST_LENGTH); return hash; } /// Returns sha256 hash value from input stream. static std::string sha256(std::istream &stream, std::size_t iterations = 1) noexcept { auto evp_md = EVP_sha256(); auto hash = stream_digest(stream, evp_md, SHA256_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, SHA256_DIGEST_LENGTH); return hash; } /// Returns sha512 hash value from input string. static std::string sha512(const std::string &input, std::size_t iterations = 1) noexcept { auto evp_md = EVP_sha512(); auto hash = message_digest(input, evp_md, SHA512_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, SHA512_DIGEST_LENGTH); return hash; } /// Returns sha512 hash value from input stream. static std::string sha512(std::istream &stream, std::size_t iterations = 1) noexcept { auto evp_md = EVP_sha512(); auto hash = stream_digest(stream, evp_md, SHA512_DIGEST_LENGTH); for(std::size_t i = 1; i < iterations; ++i) hash = message_digest(hash, evp_md, SHA512_DIGEST_LENGTH); return hash; } /** * Returns PBKDF2 derived key from the given password. * * @param password The password to derive key from. * @param salt The salt to be used in the algorithm. * @param iterations Number of iterations to be used in the algorithm. * @param key_size Number of bytes of the returned key. * * @return The PBKDF2 derived key. */ static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) noexcept { std::string key(static_cast(key_size), '\0'); PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(), reinterpret_cast(salt.c_str()), salt.size(), iterations, key_size, reinterpret_cast(&key[0])); return key; } }; } // namespace SimpleWeb #endif /* SIMPLE_WEB_CRYPTO_HPP */