LCOV - code coverage report
Current view: top level - src/bcrypt - hash.cpp (source / functions) Coverage Total Hit Missed
Test: coverage_remapped.info Lines: 77.4 % 62 48 14
Test Date: 2026-06-13 19:44:58 Functions: 100.0 % 5 5

           TLA  Line data    Source code
       1                 : //
       2                 : // Copyright (c) 2025 Vinnie Falco (vinnie.falco@gmail.com)
       3                 : //
       4                 : // Distributed under the Boost Software License, Version 1.0. (See accompanying
       5                 : // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
       6                 : //
       7                 : // Official repository: https://github.com/cppalliance/http
       8                 : //
       9                 : 
      10                 : #include <boost/http/bcrypt.hpp>
      11                 : #include <boost/http/detail/except.hpp>
      12                 : #include "base64.hpp"
      13                 : #include "crypt.hpp"
      14                 : 
      15                 : namespace boost {
      16                 : namespace http {
      17                 : namespace bcrypt {
      18                 : 
      19                 : result
      20 HIT           7 : gen_salt(
      21                 :     unsigned rounds,
      22                 :     version ver)
      23                 : {
      24                 :     // Validate preconditions
      25               7 :     if (rounds < 4 || rounds > 31)
      26 MIS           0 :         http::detail::throw_invalid_argument("bcrypt rounds must be 4-31");
      27                 : 
      28                 :     // Generate random salt
      29                 :     std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
      30 HIT           7 :     detail::generate_salt_bytes(salt_bytes);
      31                 : 
      32                 :     // Format salt string
      33               7 :     result r;
      34               7 :     std::size_t len = detail::format_salt(
      35                 :         r.buf(),
      36                 :         salt_bytes,
      37                 :         rounds,
      38                 :         ver);
      39                 : 
      40               7 :     r.set_size(static_cast<unsigned char>(len));
      41              14 :     return r;
      42                 : }
      43                 : 
      44                 : result
      45              13 : hash(
      46                 :     core::string_view password,
      47                 :     unsigned rounds,
      48                 :     version ver)
      49                 : {
      50                 :     // Validate preconditions
      51              13 :     if (rounds < 4 || rounds > 31)
      52 MIS           0 :         http::detail::throw_invalid_argument("bcrypt rounds must be 4-31");
      53                 : 
      54                 :     // Generate random salt
      55                 :     std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
      56 HIT          13 :     detail::generate_salt_bytes(salt_bytes);
      57                 : 
      58                 :     // Hash password
      59                 :     std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN];
      60              13 :     detail::bcrypt_hash(
      61                 :         password.data(),
      62                 :         password.size(),
      63                 :         salt_bytes,
      64                 :         rounds,
      65                 :         hash_bytes);
      66                 : 
      67                 :     // Format output
      68              13 :     result r;
      69              13 :     std::size_t len = detail::format_hash(
      70                 :         r.buf(),
      71                 :         salt_bytes,
      72                 :         hash_bytes,
      73                 :         rounds,
      74                 :         ver);
      75                 : 
      76              13 :     r.set_size(static_cast<unsigned char>(len));
      77              26 :     return r;
      78                 : }
      79                 : 
      80                 : result
      81               7 : hash(
      82                 :     core::string_view password,
      83                 :     core::string_view salt,
      84                 :     system::error_code& ec)
      85                 : {
      86               7 :     ec = {};
      87                 : 
      88                 :     // Parse salt
      89                 :     version ver;
      90                 :     unsigned rounds;
      91                 :     std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
      92                 : 
      93               7 :     if (!detail::parse_salt(salt, ver, rounds, salt_bytes))
      94                 :     {
      95               2 :         ec = make_error_code(error::invalid_salt);
      96               2 :         return result{};
      97                 :     }
      98                 : 
      99                 :     // Hash password
     100                 :     std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN];
     101               5 :     detail::bcrypt_hash(
     102                 :         password.data(),
     103                 :         password.size(),
     104                 :         salt_bytes,
     105                 :         rounds,
     106                 :         hash_bytes);
     107                 : 
     108                 :     // Format output
     109               5 :     result r;
     110               5 :     std::size_t len = detail::format_hash(
     111                 :         r.buf(),
     112                 :         salt_bytes,
     113                 :         hash_bytes,
     114                 :         rounds,
     115                 :         ver);
     116                 : 
     117               5 :     r.set_size(static_cast<unsigned char>(len));
     118               5 :     return r;
     119                 : }
     120                 : 
     121                 : bool
     122              14 : compare(
     123                 :     core::string_view password,
     124                 :     core::string_view hash_str,
     125                 :     system::error_code& ec)
     126                 : {
     127              14 :     ec = {};
     128                 : 
     129                 :     // Parse hash to extract salt
     130                 :     version ver;
     131                 :     unsigned rounds;
     132                 :     std::uint8_t salt_bytes[detail::BCRYPT_SALT_LEN];
     133                 : 
     134              14 :     if (!detail::parse_salt(hash_str, ver, rounds, salt_bytes))
     135                 :     {
     136               4 :         ec = make_error_code(error::invalid_hash);
     137               4 :         return false;
     138                 :     }
     139                 : 
     140                 :     // Validate hash length
     141              10 :     if (hash_str.size() != detail::BCRYPT_HASH_OUTPUT_LEN)
     142                 :     {
     143 MIS           0 :         ec = make_error_code(error::invalid_hash);
     144               0 :         return false;
     145                 :     }
     146                 : 
     147                 :     // Decode stored hash (31 base64 chars starting at position 29)
     148                 :     std::uint8_t stored_hash[detail::BCRYPT_HASH_LEN];
     149 HIT          10 :     int decoded = detail::base64_decode(
     150                 :         stored_hash,
     151              10 :         hash_str.data() + 29,
     152                 :         31);
     153                 : 
     154              10 :     if (decoded < 0)
     155                 :     {
     156 MIS           0 :         ec = make_error_code(error::invalid_hash);
     157               0 :         return false;
     158                 :     }
     159                 : 
     160                 :     // Compute hash of provided password
     161                 :     std::uint8_t computed_hash[detail::BCRYPT_HASH_LEN];
     162 HIT          10 :     detail::bcrypt_hash(
     163                 :         password.data(),
     164                 :         password.size(),
     165                 :         salt_bytes,
     166                 :         rounds,
     167                 :         computed_hash);
     168                 : 
     169                 :     // Constant-time comparison (only first 23 bytes are used)
     170              10 :     return detail::secure_compare(stored_hash, computed_hash, 23);
     171                 : }
     172                 : 
     173                 : unsigned
     174               4 : get_rounds(
     175                 :     core::string_view hash_str,
     176                 :     system::error_code& ec)
     177                 : {
     178               4 :     ec = {};
     179                 : 
     180                 :     // Minimum length check
     181               4 :     if (hash_str.size() < 7)
     182                 :     {
     183 MIS           0 :         ec = make_error_code(error::invalid_hash);
     184               0 :         return 0;
     185                 :     }
     186                 : 
     187 HIT           4 :     char const* s = hash_str.data();
     188                 : 
     189                 :     // Check prefix
     190               4 :     if (s[0] != '$' || s[1] != '2')
     191                 :     {
     192               2 :         ec = make_error_code(error::invalid_hash);
     193               2 :         return 0;
     194                 :     }
     195                 : 
     196                 :     // Check version character
     197               2 :     if ((s[2] != 'a' && s[2] != 'b' && s[2] != 'y') || s[3] != '$')
     198                 :     {
     199 MIS           0 :         ec = make_error_code(error::invalid_hash);
     200               0 :         return 0;
     201                 :     }
     202                 : 
     203                 :     // Parse rounds
     204 HIT           2 :     if (s[4] < '0' || s[4] > '9' || s[5] < '0' || s[5] > '9')
     205                 :     {
     206 MIS           0 :         ec = make_error_code(error::invalid_hash);
     207               0 :         return 0;
     208                 :     }
     209                 : 
     210 HIT           2 :     unsigned rounds = static_cast<unsigned>((s[4] - '0') * 10 + (s[5] - '0'));
     211               2 :     if (rounds < 4 || rounds > 31)
     212                 :     {
     213 MIS           0 :         ec = make_error_code(error::invalid_hash);
     214               0 :         return 0;
     215                 :     }
     216                 : 
     217 HIT           2 :     return rounds;
     218                 : }
     219                 : 
     220                 : } // bcrypt
     221                 : } // http
     222                 : } // boost
        

Generated by: LCOV version 2.3