aboutsummaryrefslogtreecommitdiffstats
path: root/src/constructs/passhash/passhash9.cpp
blob: 6618f36fa131cb9caefc899e5cb36ce98c93f6f9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
* Passhash9 Password Hashing
* (C) 2010 Jack Lloyd
*
* Distributed under the terms of the Botan license
*/

#include <botan/passhash9.h>
#include <botan/loadstor.h>
#include <botan/libstate.h>
#include <botan/pbkdf2.h>
#include <botan/base64.h>
#include <botan/pipe.h>

namespace Botan {

namespace {

const std::string MAGIC_PREFIX = "$9$";

const u32bit WORKFACTOR_BYTES = 2;
const u32bit ALGID_BYTES = 1;
const u32bit SALT_BYTES = 12; // 96 bits of salt
const u32bit PASSHASH9_PBKDF_OUTPUT_LEN = 24; // 192 bits output

const byte PASSHASH9_DEFAULT_ALGO = 0; // HMAC(SHA-1)

const u32bit WORK_FACTOR_SCALE = 10000;

MessageAuthenticationCode* get_pbkdf_prf(byte alg_id)
   {
   Algorithm_Factory& af = global_state().algorithm_factory();

   try
      {
      if(alg_id == 0)
         return af.make_mac("HMAC(SHA-1)");
      else if(alg_id == 1)
         return af.make_mac("HMAC(SHA-256)");
      else if(alg_id == 2)
         return af.make_mac("CMAC(Blowfish)");
      }
   catch(Algorithm_Not_Found) {}

   return 0;
   }

}

std::string generate_passhash9(const std::string& pass,
                               RandomNumberGenerator& rng,
                               u16bit work_factor)
   {
   return generate_passhash9(pass, PASSHASH9_DEFAULT_ALGO, rng, work_factor);
   }

std::string generate_passhash9(const std::string& pass,
                               byte alg_id,
                               RandomNumberGenerator& rng,
                               u16bit work_factor)
   {
   MessageAuthenticationCode* prf = get_pbkdf_prf(alg_id);

   if(!prf)
      throw Invalid_Argument("Passhash9: Algorithm id " + to_string(alg_id) +
                             " is not defined");

   PKCS5_PBKDF2 kdf(prf); // takes ownership of pointer

   SecureVector<byte> salt(SALT_BYTES);
   rng.randomize(&salt[0], salt.size());

   u32bit kdf_iterations = WORK_FACTOR_SCALE * work_factor;

   SecureVector<byte> pbkdf2_output =
      kdf.derive_key(PASSHASH9_PBKDF_OUTPUT_LEN,
                     pass,
                     &salt[0], salt.size(),
                     kdf_iterations).bits_of();

   Pipe pipe(new Base64_Encoder);
   pipe.start_msg();
   pipe.write(alg_id);
   pipe.write(get_byte(0, work_factor));
   pipe.write(get_byte(1, work_factor));
   pipe.write(salt);
   pipe.write(pbkdf2_output);
   pipe.end_msg();

   return MAGIC_PREFIX + pipe.read_all_as_string();
   }

bool check_passhash9(const std::string& pass, const std::string& hash)
   {
   const u32bit BINARY_LENGTH =
     ALGID_BYTES +
     WORKFACTOR_BYTES +
     PASSHASH9_PBKDF_OUTPUT_LEN +
     SALT_BYTES;

   const u32bit BASE64_LENGTH =
      MAGIC_PREFIX.size() + (BINARY_LENGTH * 8) / 6;

   if(hash.size() != BASE64_LENGTH)
      return false;

   for(size_t i = 0; i != MAGIC_PREFIX.size(); ++i)
      if(hash[i] != MAGIC_PREFIX[i])
         return false;

   Pipe pipe(new Base64_Decoder);
   pipe.start_msg();
   pipe.write(hash.c_str() + MAGIC_PREFIX.size());
   pipe.end_msg();

   SecureVector<byte> bin = pipe.read_all();

   if(bin.size() != BINARY_LENGTH)
      return false;

   byte alg_id = bin[0];

   u32bit kdf_iterations =
      WORK_FACTOR_SCALE * load_be<u16bit>(bin + ALGID_BYTES, 0);

   if(kdf_iterations == 0)
      return false;

   MessageAuthenticationCode* pbkdf_prf = get_pbkdf_prf(alg_id);

   if(pbkdf_prf == 0)
      return false; // unknown algorithm, reject

   PKCS5_PBKDF2 kdf(pbkdf_prf); // takes ownership of pointer

   SecureVector<byte> cmp = kdf.derive_key(
      PASSHASH9_PBKDF_OUTPUT_LEN,
      pass,
      &bin[ALGID_BYTES + WORKFACTOR_BYTES], SALT_BYTES,
      kdf_iterations).bits_of();

   return same_mem(cmp.begin(),
                   bin.begin() + ALGID_BYTES + WORKFACTOR_BYTES + SALT_BYTES,
                   PASSHASH9_PBKDF_OUTPUT_LEN);
   }

}