hmac.cpp 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. #include "hmac.h"
  2. #include <Windows.h>
  3. #include <algorithm>
  4. #include <bcrypt.h>
  5. #include <limits>
  6. #include <vector>
  7. #include "murmur3.h"
  8. namespace sunrise::middleware::crypto::hmac {
  9. namespace {
  10. /** SHA-1 produces this many bytes. */
  11. constexpr std::size_t kSha1DigestSize = 20;
  12. /** The hand-built construction pads the key to one block. */
  13. constexpr std::size_t kPadBlockSize = 64;
  14. /** Byte the inner pad is built with. */
  15. constexpr std::byte kInnerPad{0x36};
  16. /** Byte the outer pad is built with. */
  17. constexpr std::byte kOuterPad{0x5C};
  18. /**
  19. * Authenticates with the hand-built construction the peer uses over a one-shot digest.
  20. * The key must fit one block. It is zero padded, never hashed down.
  21. * @param key Authentication key.
  22. * @param first Leading covered bytes.
  23. * @param second Trailing covered bytes.
  24. * @param output Receives the digest.
  25. */
  26. void authenticate_murmur3(std::span<const std::byte> key,
  27. std::span<const std::byte> first,
  28. std::span<const std::byte> second,
  29. Digest& output) noexcept {
  30. std::array<std::byte, kPadBlockSize> inner{};
  31. std::array<std::byte, kPadBlockSize> outer{};
  32. std::copy(key.begin(), key.end(), inner.begin());
  33. std::copy(key.begin(), key.end(), outer.begin());
  34. for (std::size_t index = 0; index < kPadBlockSize; ++index) {
  35. inner[index] ^= kInnerPad;
  36. outer[index] ^= kOuterPad;
  37. }
  38. std::vector<std::byte> body;
  39. body.reserve(inner.size() + first.size() + second.size());
  40. body.insert(body.end(), inner.begin(), inner.end());
  41. body.insert(body.end(), first.begin(), first.end());
  42. body.insert(body.end(), second.begin(), second.end());
  43. murmur3::Digest digest{};
  44. murmur3::hash(body, {}, digest);
  45. murmur3::Digest sealed{};
  46. murmur3::hash(outer, digest, sealed);
  47. output.size = murmur3::kDigestSize;
  48. std::copy(sealed.begin(), sealed.end(), output.bytes.begin());
  49. // The pads and the body carry the key.
  50. SecureZeroMemory(inner.data(), inner.size());
  51. SecureZeroMemory(outer.data(), outer.size());
  52. SecureZeroMemory(body.data(), body.size());
  53. }
  54. /** @return True for a BCrypt status that reports success. */
  55. [[nodiscard]] bool succeeded(NTSTATUS status) noexcept {
  56. return status >= 0;
  57. }
  58. /** @param algorithm Selected digest. @return The Windows provider name. */
  59. [[nodiscard]] LPCWSTR provider(Algorithm algorithm) noexcept {
  60. return algorithm == Algorithm::sha1 ? BCRYPT_SHA1_ALGORITHM : BCRYPT_SHA256_ALGORITHM;
  61. }
  62. /** @param algorithm Selected digest. @return Its digest width in bytes. */
  63. [[nodiscard]] std::size_t digest_size(Algorithm algorithm) noexcept {
  64. return algorithm == Algorithm::sha1 ? kSha1DigestSize : kMaximumDigestSize;
  65. }
  66. /**
  67. * Adds one buffer to an open digest.
  68. * @param handle Open hash object.
  69. * @param part Bytes to add; an empty part is skipped.
  70. * @return True when the bytes fit one call and BCrypt accepted them.
  71. */
  72. [[nodiscard]] bool add(BCRYPT_HASH_HANDLE handle, std::span<const std::byte> part) noexcept {
  73. if (part.empty()) {
  74. return true;
  75. }
  76. if (part.size() > (std::numeric_limits<ULONG>::max)()) {
  77. return false;
  78. }
  79. return succeeded(BCryptHashData(handle,
  80. reinterpret_cast<PUCHAR>(const_cast<std::byte*>(part.data())),
  81. static_cast<ULONG>(part.size()),
  82. 0));
  83. }
  84. } // namespace
  85. /** Authenticates two buffers as one message. */
  86. bool authenticate(Algorithm algorithm,
  87. std::span<const std::byte> key,
  88. std::span<const std::byte> first,
  89. std::span<const std::byte> second,
  90. Digest& output) noexcept {
  91. if (algorithm == Algorithm::murmur3) {
  92. if (key.size() > kPadBlockSize) {
  93. return false;
  94. }
  95. authenticate_murmur3(key, first, second, output);
  96. return true;
  97. }
  98. BCRYPT_ALG_HANDLE opened = nullptr;
  99. if (!succeeded(BCryptOpenAlgorithmProvider(
  100. &opened, provider(algorithm), nullptr, BCRYPT_ALG_HANDLE_HMAC_FLAG))) {
  101. return false;
  102. }
  103. output.size = digest_size(algorithm);
  104. BCRYPT_HASH_HANDLE handle = nullptr;
  105. bool complete = false;
  106. if (succeeded(BCryptCreateHash(opened,
  107. &handle,
  108. nullptr,
  109. 0,
  110. reinterpret_cast<PUCHAR>(const_cast<std::byte*>(key.data())),
  111. static_cast<ULONG>(key.size()),
  112. 0))) {
  113. complete = add(handle, first) && add(handle, second)
  114. && succeeded(BCryptFinishHash(handle,
  115. reinterpret_cast<PUCHAR>(output.bytes.data()),
  116. static_cast<ULONG>(output.size),
  117. 0));
  118. BCryptDestroyHash(handle);
  119. }
  120. BCryptCloseAlgorithmProvider(opened, 0);
  121. if (!complete) {
  122. output = {};
  123. }
  124. return complete;
  125. }
  126. } // namespace sunrise::middleware::crypto::hmac