10
$\begingroup$

Microsoft has created an implementation of PBKDF1 in the PasswordDeriveBytes class. It can however generate more bytes than PBKDF1, which is limited to the number of bytes generated by the underlying hash function. How is this proprietary extension of Microsoft defined?

$\endgroup$
0

1 Answer 1

10
$\begingroup$

The password P in the Microsoft implementation is first encoded using UTF-8. The iteration count c defaults to the value 100, and the hash algorithm Hash to SHA-1.

The output is identical to PBKDF1 up to the maximum output size of PBKDF1, which is the output size of the hash. After that the first-to-last hash is prefixed with a counter (as a string containing a decimal number encoded using ASCII) and hashed again to produce the output. If GetBytes is called multiple times it will just return the next output bytes from the stream.

Note that this description does not describe the broken implementation that Microsoft shipped with earlier Windows operating systems. Those contained a bug which could result in repetition in the output of the GetBytes method if it was called two or more times.

The following is a more formal description of the algorithm. It extends the definition of PBKDF1 in RFC 2898, section 5.1.

PBKDF1_MS (P, S, c, dkLen)

   Options:        Hash       underlying hash function

   Input:          P          password, an octet string
                   S          salt, an eight-octet string
                   c          iteration count, a positive integer
                   dkLen      intended length in octets of derived key,
                              a positive integer, bounded to 100 times
                              the output size of Hash

   Output:         DK         derived key, a dkLen-octet string

   Steps:

      1. If dkLen > 100 * Hash output
         "derived key too long" and stop.

      2. Apply the underlying hash function Hash for c iterations to the
         concatenation of the password P and the salt S,
         apply the Microsoft extension by hashing the concatenation of
         an encoded counter and the output of the hash produced for
         iteration c - 1, then extract the first dkLen octets to produce
         a derived key DK:

                   T_1 = Hash (P || S) ,
                   T_2 = Hash (T_1) ,
                   ...
                   T_c = Hash (T_{c-1}) ,
                   R_1 = T_c ,
                   R_2 = Hash (Ctr(1) || T_{c-1}) ,
                   ...
                   R_n = Hash (Ctr(n-1) || T_{c-1}) ,
                   R = R_1 || R_2 || ... || R_n
                   DK = R<0..dkLen-1>

      3. Output the derived key DK.

    The Ctr function converts the given number to a decimal
    representation in ASCII.

Original Mono implementation in C# here. Java port here.


You are strongly encouraged to use the RFC2898DeriveBytes instead, which implements PBKDF2.

If output is required of PBKDF2 that extends beyond the output size of the underlying hash then it is recommended to use a Key Based Key Derivation Function such as HKDF. If you don't have an implementation of that, then simply hash the output and a statically sized identifier to create a derived key SHA-256(master | identifier) and take as many bytes from the left as needed.

$\endgroup$
6
  • $\begingroup$ What is n in the 2nd step? $\endgroup$
    – Géry Ogam
    Commented Sep 19, 2018 at 13:50
  • $\begingroup$ Uh, I have to get back to you on that. I don't have the context anymore when I wrote this answer. And I should definitely get from my ass and do something useful other than browsing through SE. $\endgroup$
    – Maarten Bodewes
    Commented Sep 19, 2018 at 14:11
  • 1
    $\begingroup$ After reading the C# implementation, I think that n is such that (n - 1) * hLen < dkLen <= n * hLen, that is n = ceil(dkLen/hLen). $\endgroup$
    – Géry Ogam
    Commented Sep 19, 2018 at 14:32
  • 1
    $\begingroup$ Researching a pathological case, I discovered that the description above is not correct for the case c = 1. By a quirk of the implementation, PasswordDeriveBytes returns the result for c = 2 when asking for c = 1. $\endgroup$ Commented May 8, 2021 at 18:59
  • $\begingroup$ See here for a Node.js implementation that includes this case. $\endgroup$ Commented May 8, 2021 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.