LCOV - code coverage report
Current view: top level - libs/capy/src/bcrypt - hash.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 77.4 % 62 48
Test Date: 2025-12-30 20:31:35 Functions: 100.0 % 5 5

            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/capy
       8              : //
       9              : 
      10              : #include <boost/capy/bcrypt/hash.hpp>
      11              : #include <boost/capy/detail/except.hpp>
      12              : #include "base64.hpp"
      13              : #include "crypt.hpp"
      14              : 
      15              : namespace boost {
      16              : namespace capy {
      17              : namespace bcrypt {
      18              : 
      19              : result
      20            7 : gen_salt(
      21              :     unsigned rounds,
      22              :     version ver)
      23              : {
      24              :     // Validate preconditions
      25            7 :     if (rounds < 4 || rounds > 31)
      26            0 :         capy::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            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            7 : hash(
      46              :     core::string_view password,
      47              :     unsigned rounds,
      48              :     version ver)
      49              : {
      50              :     // Validate preconditions
      51            7 :     if (rounds < 4 || rounds > 31)
      52            0 :         capy::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            7 :     detail::generate_salt_bytes(salt_bytes);
      57              : 
      58              :     // Hash password
      59              :     std::uint8_t hash_bytes[detail::BCRYPT_HASH_LEN];
      60            7 :     detail::bcrypt_hash(
      61              :         password.data(),
      62              :         password.size(),
      63              :         salt_bytes,
      64              :         rounds,
      65              :         hash_bytes);
      66              : 
      67              :     // Format output
      68            7 :     result r;
      69            7 :     std::size_t len = detail::format_hash(
      70              :         r.buf(),
      71              :         salt_bytes,
      72              :         hash_bytes,
      73              :         rounds,
      74              :         ver);
      75              : 
      76            7 :     r.set_size(static_cast<unsigned char>(len));
      77           14 :     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            8 : compare(
     123              :     core::string_view password,
     124              :     core::string_view hash_str,
     125              :     system::error_code& ec)
     126              : {
     127            8 :     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            8 :     if (!detail::parse_salt(hash_str, ver, rounds, salt_bytes))
     135              :     {
     136            2 :         ec = make_error_code(error::invalid_hash);
     137            2 :         return false;
     138              :     }
     139              : 
     140              :     // Validate hash length
     141            6 :     if (hash_str.size() != detail::BCRYPT_HASH_OUTPUT_LEN)
     142              :     {
     143            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            6 :     int decoded = detail::base64_decode(
     150              :         stored_hash,
     151            6 :         hash_str.data() + 29,
     152              :         31);
     153              : 
     154            6 :     if (decoded < 0)
     155              :     {
     156            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            6 :     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            6 :     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            0 :         ec = make_error_code(error::invalid_hash);
     184            0 :         return 0;
     185              :     }
     186              : 
     187            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            0 :         ec = make_error_code(error::invalid_hash);
     200            0 :         return 0;
     201              :     }
     202              : 
     203              :     // Parse rounds
     204            2 :     if (s[4] < '0' || s[4] > '9' || s[5] < '0' || s[5] > '9')
     205              :     {
     206            0 :         ec = make_error_code(error::invalid_hash);
     207            0 :         return 0;
     208              :     }
     209              : 
     210            2 :     unsigned rounds = static_cast<unsigned>((s[4] - '0') * 10 + (s[5] - '0'));
     211            2 :     if (rounds < 4 || rounds > 31)
     212              :     {
     213            0 :         ec = make_error_code(error::invalid_hash);
     214            0 :         return 0;
     215              :     }
     216              : 
     217            2 :     return rounds;
     218              : }
     219              : 
     220              : } // bcrypt
     221              : } // capy
     222              : } // boost
     223              : 
        

Generated by: LCOV version 2.1