CppSecurity  1.1.1.0
C++ Security Library
google_authenticator.cpp
Go to the documentation of this file.
1 
10 
11 #include "errors/exceptions.h"
12 #include "memory/memory.h"
13 #include "string/encoding.h"
14 #include "string/format.h"
15 #include "utility/countof.h"
16 
17 #include <cassert>
18 
19 #include <openssl/evp.h>
20 #include <openssl/hmac.h>
21 
22 namespace CppSecurity {
23 
24 GoogleAuthenticator::GoogleAuthenticator(size_t secret_length, size_t pin_length)
25  : _secret_length(secret_length), _pin_length(pin_length)
26 {
27  assert((secret_length >= 6) && "Secret should be at least 6 bytes!");
28  if (secret_length < 6)
29  throwex CppCommon::SecurityException("Invalid secret length!");
30  assert(((pin_length >= 3) && (pin_length <= 10)) && "Pin should be at least 3 digits and not more than 10 digits!");
31  if ((pin_length < 3) || (pin_length > 10))
32  throwex CppCommon::SecurityException("Invalid pin length!");
33 }
34 
36 {
37  std::string result(secret_length(), 0);
38  CppCommon::Memory::CryptoFill(result.data(), result.size());
39  return result;
40 }
41 
42 std::password GoogleAuthenticator::GenerateSecret(std::string_view password) const
43 {
44  return std::password(CppCommon::Encoding::Base32Encode(password));
45 }
46 
47 std::password GoogleAuthenticator::GenerateSecret(std::string_view password, std::string_view salt) const
48 {
49  // Compute the HMAC-SHA1 of the secret and the challenge
50  uint8_t hash[EVP_MAX_MD_SIZE];
51  unsigned int size = EVP_MAX_MD_SIZE;
52  if (HMAC(EVP_sha1(), password.data(), (int)password.size(), (const uint8_t*)salt.data(), (int)salt.size(), hash, &size) == nullptr)
53  throwex CppCommon::SecurityException("HMAC-SHA1 calculation error!");
54 
55  // Generate the secret
56  std::password result(secret_length(), 0);
57  for (size_t i = 0; i < result.size(); ++i)
58  result[i] = hash[i % size];
59  return GenerateSecret(result);
60 }
61 
62 std::password GoogleAuthenticator::GenerateURL(std::string_view identifier, std::string_view secret) const
63 {
64  return std::password(CppCommon::Encoding::URLEncode(CppCommon::format("otpauth://totp/{}?secret={}", identifier, secret)));
65 }
66 
67 std::password GoogleAuthenticator::GenerateQRCodeLink(std::string_view url, size_t width, size_t height) const
68 {
69  return std::password(CppCommon::format("https://chart.apis.google.com/chart?cht=qr&chs={}x{}&chl={}", width, height, url));
70 }
71 
72 size_t GoogleAuthenticator::GenerateToken(std::string_view secret, const CppCommon::Timestamp& timestamp) const
73 {
74  // Get the current timestamp in 30sec intervals
75  uint64_t seconds = timestamp.seconds() / 30;
76 
77  // Perform big-endian timestamp conversion
78  uint8_t challenge[8];
79  for (size_t i = 8; i--; seconds >>= 8)
80  challenge[i] = seconds & 0xFF;
81 
82  // Decode the secret from the Base32 encoding
83  std::password key(CppCommon::Encoding::Base32Decode(secret));
84 
85  // Compute the HMAC-SHA1 of the secret and the challenge
86  uint8_t hash[EVP_MAX_MD_SIZE];
87  unsigned int size = EVP_MAX_MD_SIZE;
88  if (HMAC(EVP_sha1(), key.data(), (int)key.size(), challenge, CppCommon::countof(challenge), hash, &size) == nullptr)
89  throwex CppCommon::SecurityException("HMAC-SHA1 calculation error!");
90 
91  // Pick the offset where to sample our hash value for the actual verification code
92  size_t offset = hash[size - 1] & 0xF;
93 
94  // Compute the truncated hash in a byte-order independent loop
95  size_t truncated = 0;
96  for (size_t i = 0; i < 4; ++i)
97  {
98  truncated <<= 8;
99  truncated |= hash[offset + i];
100  }
101 
102  // Compute the pin scale
103  size_t pin = 1;
104  for (size_t i = 0; i < pin_length(); ++i)
105  pin *= 10;
106 
107  // Truncate to a smaller number of digits
108  truncated &= 0x7FFFFFFF;
109  truncated %= pin;
110  return truncated;
111 }
112 
113 bool GoogleAuthenticator::Validate(size_t token, std::string_view secret, const CppCommon::Timestamp& timestamp) const
114 {
115  return (token == GenerateToken(secret, timestamp));
116 }
117 
118 bool GoogleAuthenticator::Validate(size_t token, std::string_view password, std::string_view salt, const CppCommon::Timestamp& timestamp) const
119 {
120  return (token == GenerateToken(GenerateSecret(password, salt), timestamp));
121 }
122 
123 } // namespace CppSecurity
size_t pin_length() const noexcept
Get the pin length.
size_t GenerateToken(std::string_view secret, const CppCommon::Timestamp &timestamp=CppCommon::UtcTimestamp()) const
Generate the Google Authenticator token for the given secret and UTC timestamp.
std::password GenerateURL(std::string_view identifier, std::string_view secret) const
Generate the Google Authenticator URL.
std::string GenerateSalt() const
Generate the unique password salt.
GoogleAuthenticator(size_t secret_length=12, size_t pin_length=6)
Initialize Google Authenticator with required parameters.
std::password GenerateQRCodeLink(std::string_view url, size_t width=100, size_t height=100) const
Generate the Google Authenticator QR Code link.
std::password GenerateSecret(std::string_view password) const
Generate the Google Authenticator secret from the given user password.
size_t secret_length() const noexcept
Get the secret length.
bool Validate(size_t token, std::string_view secret, const CppCommon::Timestamp &timestamp=CppCommon::UtcTimestamp()) const
Validate the Google Authenticator token over the given secret and UTC timestamp.
Password string.
Definition: password.h:53
Google Authenticator definition.