views:

145

answers:

2

I'm trying to create a Delphi version of this algorithm:

void PWSfileV3::StretchKey(const unsigned char *salt, unsigned long saltLen,
                           const StringX &passkey,
                           unsigned int N, unsigned char *Ptag)
{
  /*
  * P' is the "stretched key" of the user's passphrase and the SALT, as defined
  * by the hash-function-based key stretching algorithm in
  * http://www.schneier.com/paper-low-entropy.pdf (Section 4.1), with SHA-256
  * as the hash function, and N iterations.
  */
  int passLen = 0;
  unsigned char *pstr = NULL;

  ConvertString(passkey, pstr, passLen);
  unsigned char *X = Ptag;
  SHA256 H0;
  H0.Update(pstr, passLen);
  H0.Update(salt, saltLen);
  H0.Final(X);

#ifdef UNICODE
  trashMemory(pstr, passLen);
  delete[] pstr;
#endif

  ASSERT(N >= MIN_HASH_ITERATIONS); // minimal value we're willing to use
  for (unsigned int i = 0; i < N; i++) {
    SHA256 H;
    // The 2nd param in next line was sizeof(X) in Beta-1
    // (bug #1451422). This change broke the ability to read beta-1
    // generated databases. If this is really needed, we should
    // hack the read functionality to try both variants (ugh).
    H.Update(X, SHA256::HASHLEN);
    H.Final(X);
  }
}

Update: (Missing function)

void ConvertString(const StringX &text,
                   unsigned char *&txt,
                   int &txtlen)
{
  LPCTSTR txtstr = text.c_str(); 
  txtlen = text.length();

#ifndef UNICODE
  txt = (unsigned char *)txtstr; // don't delete[] (ugh)!!!
#else
#ifdef _WIN32
  txt = new unsigned char[3*txtlen]; // safe upper limit
  int len = WideCharToMultiByte(CP_ACP, 0, txtstr, txtlen,
    LPSTR(txt), 3*txtlen, NULL, NULL);
  ASSERT(len != 0);
#else
  mbstate_t mbs;
  memset(&mbs, '\0', sizeof(mbs));
  size_t len = wcsrtombs(NULL, &txtstr, 0, &mbs);
  txt = new unsigned char[len+1];
  len = wcsrtombs((char *)txt, &txtstr, len, &mbs);
  ASSERT(len != (size_t)-1);
#endif
  txtlen = len;
  txt[len] = '\0';
#endif /* UNICODE */
}

Here is what I've got (D2009 version):

(Please note: T256BitArray is defined as Array[0..31] of byte)

procedure StretchKey(Const Salt:T256BitArray; Const Passkey:string; Const Iterations:LongWord; Var KeyResult:T256BitArray);
var
   pStr : RawByteString;
   wHash : THash_sha256;
   loop : integer;
begin
  pStr := AnsiString(PassKey);

  wHash := THash_SHA256.Create;
  try
     wHash.Init;
     wHash.Calc(pStr[1], Length(pStr));
     wHash.Calc(Salt, Length(Salt));
     wHash.Done;
     PStr := wHash.DigestStr;
  finally
     FreeAndNil(wHash);
  end;

  for loop := 0 to Iterations-1 do
  begin
     wHash := THash_sha256.Create;
     try
        wHash.Init;
        wHash.Calc(PStr[1], wHash.DigestSize);
        wHash.Done;
        PStr := wHash.DigestStr;
     finally
        FreeAndNil(wHash);
     end;
  end;

  move(pStr[1], KeyResult, sizeof(KeyResult));
end;

The original code snippet is from the Password Safe opensource application.

I'm attempting to open an existing Password Save (v3) database for reading.

It appears that it doesn't matter what I do I can not make the algorithm generate the required hash.

In the above Delphi snippet I'm using the DEC v5.2 2009 component set. I've also tried the DCPcrypt library. Funny enough I get the same values from both libraries but nothing comes out being compatible with the hash from the PWSv3 file.

The SHA256 components I've used both pass the SHA256 test vector hashes so I'm assuming that it's something I've done wrong in recoding the method.

Am I missing something?

SOLVED: Everything is correct. The problem comes in with the converstion of the passkey string. I've figured out that I have to use the WideCharToMultiByte function to get the correct code page conversion.

A: 

The loop in the lower part should not have a wHash.Init; in it.

Brian Evans
Why? Don't you have to initialize it before using it? It's a brand new object each time through the loop, just like in the original code.
Rob Kennedy
@Rob: You are correct. The original algorithm uses, what appears to me, to be a new component instance everytime. BTW, I have tried moving the init outside of the loop and it doesn't work either.
Ryan J. Mills
A: 

I've solved the issue.

Everything with my implementation is correct. The problem comes in with the converstion of the passkey string. I've figured out that I absolutely had to use the WideCharToMultiByte function to get the correct code page conversion.

Here is the corrected code:

procedure StretchKey(Const Salt:T256BitArray; Const Passkey:string; Const Iterations:LongWord; Var KeyResult:T256BitArray);
var
   pStr : RawByteString;
   wHash : THash_sha256;
   loop : integer;
   wStr : AnsiString;
   wLen : integer;
begin
  wLen := 3*length(PassKey);
  SetLength(wStr, wLen);

  wLen := WideCharToMultiByte(GetACP, 0, PChar(PassKey), length(PassKey), PAnsiChar(wStr), wLen, nil, nil);
  SetLength(wStr, wLen);

  pStr := wStr;

  wHash := THash_SHA256.Create;
  try
     wHash.Init;
     wHash.Calc(pStr[1], Length(pStr));
     wHash.Calc(Salt, Length(Salt));
     wHash.Done;
     PStr := wHash.DigestStr;
  finally
     FreeAndNil(wHash);
  end;

  for loop := 0 to Iterations-1 do
  begin
     wHash := THash_sha256.Create;
     try
        wHash.Init;
        wHash.Calc(PStr[1], wHash.DigestSize);
        wHash.Done;
        PStr := wHash.DigestStr;
     finally
        FreeAndNil(wHash);
     end;
  end;

  move(pStr[1], KeyResult, sizeof(KeyResult));
end;
Ryan J. Mills