RFC 2759

Microsoft PPP CHAP Extensions, Version 2

πŸ“… published January 2000, from ietf

RFC 2759 πŸ’– Microsoft PPP CHAP Extensions, Version 2

(Part 1 of 3, so more is coming! πŸŽ€)

Okay bestie, here's the tea πŸ“ This one is written by G. Zorn from Microsoft Corporation, dated January 2000, and it's filed as Informational (so it's giving "here's some info," not "this is a standard" πŸ’…). The title block doesn't list anything it obsoletes or updates.


Status of This Memo ✨

This memo is here to give the Internet community information πŸ¦‹ It is NOT an Internet standard of any kind, and distribution is unlimited, so share away!

The Internet Society owns the copyright here (2000), all rights reserved.

Abstract 🌸

PPP (the Point-to-Point Protocol) [1] is the standard way to carry multi-protocol datagrams over point-to-point links πŸš€ It has an extensible Link Control Protocol plus a whole family of Network Control Protocols (NCPs) for setting up and configuring different network-layer protocols. Iconic, honestly.

This document covers version two of Microsoft's PPP CHAP dialect, called MS-CHAP-V2 πŸ’œ It's similar to MS-CHAP version one (MS-CHAP-V1, described in [9]), but NOT compatible with it. Some protocol fields got deleted, and others got reused with different meanings. MS-CHAP-V2 also adds mutual authentication, which is a major glow-up ✨

The algorithms behind the various MS-CHAP-V2 protocol fields are described in section 8, and negotiation and hash generation examples are in section 9.

Specification of Requirements πŸ“œ

So the key words "MAY", "MUST", "MUST NOT", "optional", "recommended", "SHOULD", and "SHOULD NOT" get read the way [3] explains πŸ€“ Those are the rules-words, so pay attention to them!

Table of Contents πŸ“‘

1. Introduction  . . . . . . . . . . . . . . . . . . . . . . . . .  3
2. LCP Configuration . . . . . . . . . . . . . . . . . . . . . . .  3
3. Challenge Packet  . . . . . . . . . . . . . . . . . . . . . . .  3
4. Response Packet . . . . . . . . . . . . . . . . . . . . . . . .  4
5. Success Packet  . . . . . . . . . . . . . . . . . . . . . . . .  4
6. Failure Packet  . . . . . . . . . . . . . . . . . . . . . . . .  5
7. Change-Password Packet  . . . . . . . . . . . . . . . . . . . .  6
8. Pseudocode  . . . . . . . . . . . . . . . . . . . . . . . . . .  7
8.1. GenerateNTResponse()  . . . . . . . . . . . . . . . . . . . .  7
8.2. ChallengeHash() . . . . . . . . . . . . . . . . . . . . . . .  8
8.3. NtPasswordHash()  . . . . . . . . . . . . . . . . . . . . . .  9
8.4. HashNtPasswordHash()  . . . . . . . . . . . . . . . . . . . .  9
8.5. ChallengeResponse() . . . . . . . . . . . . . . . . . . . . .  9
8.6. DesEncrypt()  . . . . . . . . . . . . . . . . . . . . . . . . 10
8.7. GenerateAuthenticatorResponse() . . . . . . . . . . . . . . . 10
8.8. CheckAuthenticatorResponse()  . . . . . . . . . . . . . . . . 12
8.9. NewPasswordEncryptedWithOldNtPasswordHash() . . . . . . . . . 12
8.10. EncryptPwBlockWithPasswordHash() . . . . . . . . . . . . . . 13
8.11. Rc4Encrypt() . . . . . . . . . . . . . . . . . . . . . . . . 13
8.12. OldNtPasswordHashEncryptedWithNewNtPasswordHash()  . . . . . 14
8.13. NtPasswordHashEncryptedWithBlock() . . . . . . . . . . . . . 14
9. Examples  . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
9.1. Negotiation Examples  . . . . . . . . . . . . . . . . . . . . 14
9.1.1. Successful authentication . . . . . . . . . . . . . . . . . 15
9.1.2. Authenticator authentication failure  . . . . . . . . . . . 15
9.1.3. Failed authentication with no retry allowed . . . . . . . . 15
9.1.4. Successful authentication after retry . . . . . . . . . . . 15
9.1.5. Failed hack attack with 3 attempts allowed  . . . . . . . . 15
9.1.6. Successful authentication with password change  . . . . . . 16
9.1.7. Successful authentication with retry and password change. . 16
9.2. Hash Example  . . . . . . . . . . . . . . . . . . . . . . . . 16
9.3. Example of DES Key Generation . . . . . . . . . . . . . . . . 17
10. Security Considerations  . . . . . . . . . . . . . . . . . . . 17
11. References . . . . . . . . . . . . . . . . . . . . . . . . . . 18
12. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 19
13. Author's Address . . . . . . . . . . . . . . . . . . . . . . . 19
14. Full Copyright Statement . . . . . . . . . . . . . . . . . . . 20

1. Introduction πŸŽ€

Where possible, MS-CHAP-V2 plays nicely with both MS-CHAP-V1 and standard CHAP 🫢 Here's the quick list of how MS-CHAP-V2 differs from MS-CHAP-V1:

  • MS-CHAP-V2 gets turned on by negotiating CHAP Algorithm 0x81 in LCP option 3, Authentication Protocol.
  • MS-CHAP-V2 gives mutual authentication between peers by piggybacking a peer challenge on the Response packet and an authenticator response on the Success packet. Mutual auth is so main character energy πŸ‘‘
  • The calculation of the "Windows NT compatible challenge response" sub-field in the Response packet now includes the peer challenge and the user name.
  • In MS-CHAP-V1, the "LAN Manager compatible challenge response" sub-field was always sent in the Response packet. In MS-CHAP-V2 that field got replaced by the Peer-Challenge field πŸ¦‹
  • The format of the Message field in the Failure packet has changed.
  • The Change Password (version 1) and Change Password (version 2) packets are no longer supported. They got swapped out for one single Change-Password packet πŸ’€

2. LCP Configuration 🌈

The LCP configuration for MS-CHAP-V2 is identical to standard CHAP's, except the Algorithm field has value 0x81 instead of the MD5 value 0x05. PPP implementations that don't support MS-CHAP-V2, but do correctly implement LCP Config-Rej, should have no problem dealing with this non-standard option. Literally the most chill fallback ever 😌

3. Challenge Packet 🧚

The MS-CHAP-V2 Challenge packet is identical in format to the standard CHAP Challenge packet, so no new drama here ✨

MS-CHAP-V2 authenticators send a 16-octet challenge Value field. Peers don't need to copy Microsoft's algorithm for picking that 16-octet value, but the standard guidelines on randomness [1,2,7] SHOULD be observed 🎲

Microsoft authenticators don't currently put anything in the Name field. That may change in the future πŸ‘€

4. Response Packet πŸ’…

The MS-CHAP-V2 Response packet is identical in format to the standard CHAP Response packet. The difference is that the Value field gets sub-formatted differently, like this:

16 octets: Peer-Challenge
 8 octets: Reserved, must be zero
24 octets: NT-Response
 1 octet : Flags

The Peer-Challenge field is a 16-octet random number 🎰 As the name suggests, the peer generates it, and it's used in calculating the NT-Response field below. Peers don't need to copy Microsoft's algorithm for picking the 16-octet value, but the standard guidelines on randomness [1,2,7] SHOULD be observed.

The NT-Response field is an encoded function of the password, the user name, the contents of the Peer-Challenge field, and the received challenge. It's output by the routine GenerateNTResponse() (see section 8.1, below). The Windows NT password is a string of 0 to (theoretically) 256 case-sensitive Unicode [8] characters. Current versions of Windows NT limit passwords to 14 characters, mainly for compatibility reasons, and this may change in the future πŸ” When computing the NT-Response field contents, only the user name is used, without any associated Windows NT domain name. That's true whether or not a Windows NT domain name shows up in the Name field (see below).

The Flag field is reserved for future use and MUST be zero 🚫

The Name field is a string of 0 to (theoretically) 256 case-sensitive ASCII characters that identifies the peer's user account name. The Windows NT domain name can prefix the user's account name, like BIGCO\johndoe, where "BIGCO" is a Windows NT domain containing the user account "johndoe". If there's no domain, the backslash should also be omitted, like johndoe πŸ“

5. Success Packet πŸ₯³

The Success packet is identical in format to the standard CHAP Success packet. The twist is that the Message field holds a 42-octet authenticator response string plus a printable message. The layout of the message field looks like this:

"S=<auth_string> M=<message>"

The <auth_string> is a 20 octet number encoded in ASCII as 40 hexadecimal digits. The hexadecimal digits A-F (if present) MUST be uppercase πŸ”  This number is derived from the challenge in the Challenge packet, the Peer-Challenge and NT-Response fields from the Response packet, and the peer password, as output by the routine GenerateAuthenticatorResponse() (see section 8.7, below). The authenticating peer MUST verify the authenticator response when a Success packet is received. The method for verifying the authenticator is described in section 8.8, below. If the authenticator response is missing or incorrect, the peer MUST end the session. Yikes, no cap, that's a hard stop πŸ›‘

The <message> is human-readable text in the appropriate charset and language [12].

6. Failure Packet πŸ™ˆ

The Failure packet is identical in format to the standard CHAP Failure packet. There's one plot twist though: the Message field holds formatted text that, unlike standard CHAP rules, actually affects how the protocol operates 😭 The Message field format is:

      "E=eeeeeeeeee R=r C=cccccccccccccccccccccccccccccccc V=vvvvvvvvvv
M=<msg>"

Here's what each piece means:

The eeeeeeeeee is the ASCII representation of a decimal error code (it doesn't need to be 10 digits). It corresponds to one of the codes listed below, though implementations should deal with codes that aren't on this list gracefully 🧯

646 ERROR_RESTRICTED_LOGON_HOURS
647 ERROR_ACCT_DISABLED
648 ERROR_PASSWD_EXPIRED
649 ERROR_NO_DIALIN_PERMISSION
691 ERROR_AUTHENTICATION_FAILURE
709 ERROR_CHANGING_PASSWORD

The r is an ASCII flag set to '1' if a retry is allowed, and '0' if not. When the authenticator sets this flag to '1', it disables short timeouts and expects the peer to prompt the user for new credentials and resubmit the response. Second chances, bestie πŸ”

The cccccccccccccccccccccccccccccccc is the ASCII representation of a hexadecimal challenge value. This field MUST be exactly 32 octets long and MUST be present 🫑

The vvvvvvvvvv is the ASCII representation of a decimal version code (it doesn't need to be 10 digits). It says which password changing protocol version the server supports. For MS-CHAP-V2, this value SHOULD always be 3 πŸ”’

<msg> is human-readable text in the appropriate charset and language [12] πŸ’¬

7. Change-Password Packet ✨

okay so the Change-Password packet is literally NOT in standard CHAP or MS-CHAP-V1 πŸ’… it's MS-CHAP-V2 only. It lets the peer change the password on the account from the Response packet right before it. You should only send it if the authenticator says ERROR_PASSWD_EXPIRED (E=648) in the Message field of the Failure packet πŸ₯Ί

This packet type works on recent Windows NT 4.0, Windows 95, and Windows 98 πŸŽ€ It does NOT work on Windows NT 3.5, Windows NT 3.51, or the early versions of Windows NT 4.0, Windows 95, and Windows 98 πŸ’€

The layout looks like this:

1 octet  : Code
1 octet  : Identifier
2 octets : Length
516 octets : Encrypted-Password
16 octets : Encrypted-Hash
16 octets : Peer-Challenge
8 octets : Reserved
24 octets : NT-Response
2-octet  : Flags

Code 7 🌸

Identifier The Identifier is one octet, and it helps match requests with replies ✨ The value is the Identifier of the Failure packet you received, plus 1. Main character math 🧚

Length 586 πŸ“

Encrypted-Password This is the PWBLOCK form of the new Windows NT password, encrypted with the old Windows NT password hash. It's the output of NewPasswordEncryptedWithOldNtPasswordHash() (see section 8.9, below) πŸ”

Encrypted-Hash This is the old Windows NT password hash, encrypted with the new Windows NT password hash. It's the output of OldNtPasswordHashEncryptedWithNewNtPasswordHash() (see section 8.12, below) πŸ’œ

Peer-Challenge A 16-octet random quantity, same as the one in the Response packet description 🎲

Reserved 8 octets, and they must be zero 🫢

NT-Response This is the NT-Response from the Response packet description, but calculated with the new password and the challenge you got in the Failure packet ✨

Flags This field is two octets. It's a bit field of option flags, where bit 0 is the least significant bit of the 16-bit value. Here's the diagram:

                    1
          5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |                               |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Bits 0-15: Reserved, always clear (0). Don't touch these bestie πŸ™ˆ

8. Pseudocode 🧠

okay this next part is the pseudocode for all the routines we've been mentioning. It's a lot, but it's giving very organised ✨

8.1. GenerateNTResponse()

GenerateNTResponse(
IN  16-octet              AuthenticatorChallenge,
IN  16-octet              PeerChallenge,
IN  0-to-256-char         UserName,

IN  0-to-256-unicode-char Password,
OUT 24-octet              Response )
{
   8-octet  Challenge
   16-octet PasswordHash

   ChallengeHash( PeerChallenge, AuthenticatorChallenge, UserName,
                  giving Challenge)

   NtPasswordHash( Password, giving PasswordHash )
   ChallengeResponse( Challenge, PasswordHash, giving Response )
}

8.2. ChallengeHash()

ChallengeHash(
IN 16-octet               PeerChallenge,
IN 16-octet               AuthenticatorChallenge,
IN  0-to-256-char         UserName,
OUT 8-octet               Challenge
{

   /*
    * SHAInit(), SHAUpdate() and SHAFinal() functions are an
    * implementation of Secure Hash Algorithm (SHA-1) [11]. These are
    * available in public domain or can be licensed from
    * RSA Data Security, Inc.
    */

   SHAInit(Context)
   SHAUpdate(Context, PeerChallenge, 16)
   SHAUpdate(Context, AuthenticatorChallenge, 16)

   /*
    * Only the user name (as presented by the peer and
    * excluding any prepended domain name)
    * is used as input to SHAUpdate().
    */

   SHAUpdate(Context, UserName, strlen(Username))
   SHAFinal(Context, Digest)
   memcpy(Challenge, Digest, 8)
}

8.3. NtPasswordHash()

NtPasswordHash(
IN  0-to-256-unicode-char Password,
OUT 16-octet              PasswordHash )
{
   /*
    * Use the MD4 algorithm [5] to irreversibly hash Password
    * into PasswordHash.  Only the password is hashed without
    * including any terminating 0.
    */
}

8.4. HashNtPasswordHash()

HashNtPasswordHash(
IN  16-octet PasswordHash,
OUT 16-octet PasswordHashHash )
{
   /*
    * Use the MD4 algorithm [5] to irreversibly hash
    * PasswordHash into PasswordHashHash.
    */
}

8.5. ChallengeResponse()

ChallengeResponse(
IN  8-octet  Challenge,
IN  16-octet PasswordHash,
OUT 24-octet Response )
{
   Set ZPasswordHash to PasswordHash zero-padded to 21 octets

   DesEncrypt( Challenge,
               1st 7-octets of ZPasswordHash,
               giving 1st 8-octets of Response )

   DesEncrypt( Challenge,
               2nd 7-octets of ZPasswordHash,
               giving 2nd 8-octets of Response )

   DesEncrypt( Challenge,
               3rd 7-octets of ZPasswordHash,
               giving 3rd 8-octets of Response )
}

8.6. DesEncrypt()

DesEncrypt(
IN  8-octet Clear,
IN  7-octet Key,
OUT 8-octet Cypher )
{
   /*
    * Use the DES encryption algorithm [4] in ECB mode [10]
    * to encrypt Clear into Cypher such that Cypher can
    * only be decrypted back to Clear by providing Key.
    * Note that the DES algorithm takes as input a 64-bit
    * stream where the 8th, 16th, 24th, etc.  bits are
    * parity bits ignored by the encrypting algorithm.
    * Unless you write your own DES to accept 56-bit input
    * without parity, you will need to insert the parity bits
    * yourself.
    */
}

8.7. GenerateAuthenticatorResponse()

GenerateAuthenticatorResponse(
IN  0-to-256-unicode-char Password,
IN  24-octet              NT-Response,
IN  16-octet              PeerChallenge,
IN  16-octet              AuthenticatorChallenge,
IN  0-to-256-char         UserName,
OUT 42-octet              AuthenticatorResponse )
{
   16-octet              PasswordHash
   16-octet              PasswordHashHash
   8-octet               Challenge

   /*
    * "Magic" constants used in response generation
    */

   Magic1[39] =
      {0x4D, 0x61, 0x67, 0x69, 0x63, 0x20, 0x73, 0x65, 0x72, 0x76,
       0x65, 0x72, 0x20, 0x74, 0x6F, 0x20, 0x63, 0x6C, 0x69, 0x65,
       0x6E, 0x74, 0x20, 0x73, 0x69, 0x67, 0x6E, 0x69, 0x6E, 0x67,
       0x20, 0x63, 0x6F, 0x6E, 0x73, 0x74, 0x61, 0x6E, 0x74};

   Magic2[41] =
      {0x50, 0x61, 0x64, 0x20, 0x74, 0x6F, 0x20, 0x6D, 0x61, 0x6B,
       0x65, 0x20, 0x69, 0x74, 0x20, 0x64, 0x6F, 0x20, 0x6D, 0x6F,
       0x72, 0x65, 0x20, 0x74, 0x68, 0x61, 0x6E, 0x20, 0x6F, 0x6E,
       0x65, 0x20, 0x69, 0x74, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6F,
       0x6E};

   /*
    * Hash the password with MD4
    */

   NtPasswordHash( Password, giving PasswordHash )

   /*
    * Now hash the hash
    */

   HashNtPasswordHash( PasswordHash, giving PasswordHashHash)

   SHAInit(Context)
   SHAUpdate(Context, PasswordHashHash, 16)
   SHAUpdate(Context, NTResponse, 24)
   SHAUpdate(Context, Magic1, 39)
   SHAFinal(Context, Digest)

   ChallengeHash( PeerChallenge, AuthenticatorChallenge, UserName,
                  giving Challenge)

   SHAInit(Context)
   SHAUpdate(Context, Digest, 20)
   SHAUpdate(Context, Challenge, 8)
   SHAUpdate(Context, Magic2, 41)
   SHAFinal(Context, Digest)

   /*
    * Encode the value of 'Digest' as "S=" followed by
    * 40 ASCII hexadecimal digits and return it in
    * AuthenticatorResponse.
    * For example,
    *   "S=0123456789ABCDEF0123456789ABCDEF01234567"
    */

}

8.8. CheckAuthenticatorResponse()

CheckAuthenticatorResponse(
IN  0-to-256-unicode-char Password,
IN  24-octet              NtResponse,
IN  16-octet              PeerChallenge,
IN  16-octet              AuthenticatorChallenge,
IN  0-to-256-char         UserName,
IN  42-octet              ReceivedResponse,
OUT Boolean               ResponseOK )
{

   20-octet MyResponse

   set ResponseOK = FALSE
   GenerateAuthenticatorResponse( Password, NtResponse, PeerChallenge,
                                  AuthenticatorChallenge, UserName,
                                  giving MyResponse)

   if (MyResponse = ReceivedResponse) then set ResponseOK = TRUE
   return ResponseOK
}

8.9. NewPasswordEncryptedWithOldNtPasswordHash()

datatype-PWBLOCK
{
   256-unicode-char Password
   4-octets         PasswordLength
}

NewPasswordEncryptedWithOldNtPasswordHash(
IN  0-to-256-unicode-char NewPassword,
IN  0-to-256-unicode-char OldPassword,
OUT datatype-PWBLOCK      EncryptedPwBlock )
{
   NtPasswordHash( OldPassword, giving PasswordHash )

   EncryptPwBlockWithPasswordHash( NewPassword,
                                   PasswordHash,
                                   giving EncryptedPwBlock )
}

8.10. EncryptPwBlockWithPasswordHash()

EncryptPwBlockWithPasswordHash(
IN  0-to-256-unicode-char Password,
IN  16-octet              PasswordHash,
OUT datatype-PWBLOCK      PwBlock )
{

   Fill ClearPwBlock with random octet values

      PwSize = lstrlenW( Password ) * sizeof( unicode-char )
      PwOffset = sizeof( ClearPwBlock.Password ) - PwSize
      Move PwSize octets to (ClearPwBlock.Password + PwOffset ) from
Password
      ClearPwBlock.PasswordLength = PwSize
      Rc4Encrypt( ClearPwBlock,
                  sizeof( ClearPwBlock ),
                  PasswordHash,
                  sizeof( PasswordHash ),
                  giving PwBlock )
   }

8.11. Rc4Encrypt()

Rc4Encrypt(
IN  x-octet Clear,
IN  integer ClearLength,
IN  y-octet Key,
IN  integer KeyLength,
OUT x-octet Cypher )
{
   /*
    * Use the RC4 encryption algorithm [6] to encrypt Clear of
    * length ClearLength octets into a Cypher of the same length
    * such that the Cypher can only be decrypted back to Clear
    * by providing a Key of length KeyLength octets.
    */
}

8.12. OldNtPasswordHashEncryptedWithNewNtPasswordHash()

OldNtPasswordHashEncryptedWithNewNtPasswordHash(
IN  0-to-256-unicode-char NewPassword,
IN  0-to-256-unicode-char OldPassword,
OUT 16-octet              EncryptedPasswordHash )
{
   NtPasswordHash( OldPassword, giving OldPasswordHash )
   NtPasswordHash( NewPassword, giving NewPasswordHash )
   NtPasswordHashEncryptedWithBlock( OldPasswordHash,
                                     NewPasswordHash,
                                     giving EncryptedPasswordHash )
}

8.13. NtPasswordHashEncryptedWithBlock()

NtPasswordHashEncryptedWithBlock(
IN  16-octet PasswordHash,
IN  16-octet Block,
OUT 16-octet Cypher )
{
   DesEncrypt( 1st 8-octets PasswordHash,
               1st 7-octets Block,
               giving 1st 8-octets Cypher )

   DesEncrypt( 2nd 8-octets PasswordHash,
               2nd 7-octets Block,
               giving 2nd 8-octets Cypher )
}

9. Examples πŸŽ€

this bit has the protocol negotiation and hash generation examples πŸ’œ

9.1. Negotiation Examples

okay so here are some examples of typical negotiations πŸ¦‹ The peer is on the left and the authenticator is on the right.

The packet sequence ID goes up by one on each authentication retry response and on the change password response. Every case where it gets updated is noted below ✨

Response retry is NEVER allowed after Change Password πŸ’€ Change Password can happen after a response retry, though 🫢

9.1. Negotiation Examples

Okay so this is where the protocol finally gets to show off πŸ’… Every example below is a little story of a call going through (or not), with the messages in order. Each arrow tells you who's sending what πŸ¦‹

9.1.1. Successful authentication

                         <- Authenticator Challenge
       Peer Response/Challenge ->
                         <- Success/Authenticator Response

   (Authenticator Response verification succeeds, call continues)

Main character energy πŸ‘‘ The challenge comes in, the peer answers, the authenticator sends back a Success with its own response, and the call just keeps going. Literally the dream πŸ’–

9.1.2. Authenticator authentication failure

                         <- Authenticator Challenge
       Peer Response/Challenge ->
                         <- Success/Authenticator Response

   (Authenticator Response verification fails, peer disconnects)

Ngl this one's a little sad 😭 Same opening moves, but the peer checks the Authenticator Response and it doesn't verify, so the peer hangs up.

9.1.3. Failed authentication with no retry allowed

                         <- Authenticator Challenge
       Peer Response/Challenge ->
                         <- Failure (E=691 R=0)

   (Authenticator disconnects)

No second chances here πŸ’€ The failure message comes back with E=691 R=0, and the authenticator is the one that disconnects.

9.1.4. Successful authentication after retry

                         <- Authenticator Challenge
       Peer Response/Challenge ->
                         <- Failure (E=691 R=1), disable short timeout
       Response (++ID) to challenge in failure message ->
                         <- Success/Authenticator Response

   (Authenticator Response verification succeeds, call continues)

Okay this is the redemption arc πŸ₯Ή The first try fails with E=691 R=1, so the peer gets to answer again with the ID incremented, and this time the Success comes through and the call continues. Iconic comeback πŸ”₯

9.1.5. Failed hack attack with 3 attempts allowed

                         <- Authenticator Challenge
       Peer Response/Challenge ->
                         <- Failure (E=691 R=1), disable short timeout
       Response (++ID) to challenge in Failure message ->
                         <- Failure (E=691 R=1), disable short timeout
       Response (++ID) to challenge in Failure message ->
                         <- Failure (E=691 R=0)

Not me looking at a hack attack with three tries πŸ™ˆ Each retry comes back as a failure with an incremented ID, and once the retries run out the last failure is E=691 R=0. Nobody gets in 🚫

9.1.6. Successful authentication with password change

                         <- Authenticator Challenge
       Peer Response/Challenge ->
                         <- Failure (E=648 R=0 V=3), disable short
   timeout
       ChangePassword (++ID) to challenge in Failure message ->
                         <- Success/Authenticator Response

   (Authenticator Response verification succeeds, call continues)

Okay so this one's a glow-up 🌸 The failure message with E=648 R=0 V=3 says it's time for a password change, so the peer sends a ChangePassword with the ID incremented, and then the Success shows up and the call continues. New password, same vibes ✨

9.1.7. Successful authentication with retry and password change

                         <- Authenticator Challenge
       Peer Response/Challenge ->
                         <- Failure (E=691 R=1), disable short timeout
       Response (++ID) to first challenge+23 ->
                         <- Failure (E=648 R=0 V=2), disable short
   timeout
       ChangePassword (++ID) to first challenge+23 ->
                         <- Success/Authenticator Response

   (Authenticator Response verification succeeds, call continues)

Lowkey the boss-level example πŸŽ€ It's a retry AND a password change in one go. The first try gets E=691 R=1, the retry goes out against the first challenge+23, then E=648 R=0 V=2 asks for the password change, the ChangePassword follows, and finally the Success lands and the call continues. Everything resolves ✨


9.2. Hash Example

Okay now the numbers 🧚 This is a worked example with the user name "User" and the password "clientPass". Heads up: all the numeric values are hexadecimal, so don't panic at the letters πŸ“

0-to-256-char UserName:
55 73 65 72

0-to-256-unicode-char Password:
63 00 6C 00 69 00 65 00 6E 00 74 00 50 00 61 00 73 00 73 00

16-octet AuthenticatorChallenge:
5B 5D 7C 7D 7B 3F 2F 3E 3C 2C 60 21 32 26 26 28

16-octet PeerChallenge:
21 40 23 24 25 5E 26 2A 28 29 5F 2B 3A 33 7C 7E

8-octet Challenge:
D0 2E 43 86 BC E9 12 26

16-octet PasswordHash:
44 EB BA 8D 53 12 B8 D6 11 47 44 11 F5 69 89 AE

24 octet NT-Response:
82 30 9E CD 8D 70 8B 5E A0 8F AA 39 81 CD 83 54 42 33 11 4A 3D 85 D6 DF

16-octet PasswordHashHash:
41 C0 0C 58 4B D2 D9 1C 40 17 A2 A1 2F A5 9F 3F

42-octet AuthenticatorResponse:
"S=407A5589115FD0D6209F510FE9C04566932CDA56"

9.3. Example of DES Key Generation

Okay so DES keys are 56 bits, but they get padded out to 64 bits by adding parity bits πŸ” Once the parity is sorted, every eighth bit is a parity bit, and the number of set bits in each octet is odd. That's odd parity, bestie πŸ’œ Fun twist: lots of DES engines don't even check parity, they just strip those bits out πŸ™ˆ

This example takes the password "MyPw" and uses it to generate a pair of DES keys. These are the kind of keys you'd use for the NtPasswordHashEncryptedWithBlock() thing from section 8.13 ✨

0-to-256-unicode-char Password:
4D 79 50 77

16-octet PasswordHash:
FC 15 6A F7 ED CD 6C 0E DD E3 33 7D 42 7F 4E AC

First "raw" DES key (initial 7 octets of password hash):
FC 15 6A F7 ED CD 6C

First parity-corrected DES key (eight octets):
FD 0B 5B 5E 7F 6E 34 D9

Second "raw" DES key (second 7 octets of password hash)
0E DD E3 33 7D 42 7F

Second parity-corrected DES key (eight octets):
0E 6E 79 67 37 EA 08 FE

10. Security Considerations ✨

Okay this part is serious, so listen up 🚨 As an implementation detail, the authenticator SHOULD limit the number of password retries allowed. Why? Because it makes brute-force password guessing attacks way harder 😀 Don't let the attacker get unlimited tries, periodt πŸ’€


11. References

  • [1] Simpson, W., "The Point-to-Point Protocol (PPP)", STD 51, RFC 1661, July 1994.
  • [2] Simpson, W., "PPP Challenge Handshake Authentication Protocol (CHAP)", RFC 1994, August 1996.
  • [3] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, March 1997.
  • [4] "Data Encryption Standard (DES)", Federal Information Processing Standard Publication 46-2, National Institute of Standards and Technology, December 1993.
  • [5] Rivest, R., "MD4 Message Digest Algorithm", RFC 1320, April 1992.
  • [6] RC4 is a proprietary encryption algorithm available under license from RSA Data Security Inc. For licensing information, contact:
RSA Data Security, Inc.
100 Marine Parkway
Redwood City, CA 94065-1031
  • [7] Eastlake, D., Crocker, S. and J. Schiller, "Randomness Recommendations for Security", RFC 1750, December 1994.
  • [8] "The Unicode Standard, Version 2.0", The Unicode Consortium, Addison-Wesley, 1996. ISBN 0-201-48345-9.
  • [9] Zorn, G. and Cobb, S., "Microsoft PPP CHAP Extensions", RFC 2433, October 1998.
  • [10] "DES Modes of Operation", Federal Information Processing Standards Publication 81, National Institute of Standards and Technology, December 1980.
  • [11] "Secure Hash Standard", Federal Information Processing Standards Publication 180-1, National Institute of Standards and Technology, April 1995.
  • [12] Zorn, G., "PPP LCP Internationalization Configuration Option", RFC 2484, January 1999.

12. Acknowledgements

Huge thanks (in no particular order) to Bruce Johnson, Tony Bell, Paul Leach, Terence Spies, Dan Simon, Narendra Gidwani, Gurdeep Singh Pall, Jody Terrill, Brad Robel-Forrest, and Joe Davies 🫢 They all gave useful suggestions and feedback, which is the most iconic thing you can do for a spec 🌈


13. Author's Address

Questions about this memo can also be directed to:

Glen Zorn
Microsoft Corporation
One Microsoft Way
Redmond, Washington 98052

Phone: +1 425 703 1559
Fax:   +1 425 936 7329
EMail: gwz@acm.org

Copyright (C) The Internet Society (2000). All Rights Reserved.

Okay the legal part, but make it cute πŸ’… You're allowed to copy and share this document, and to translate it into other languages. You can also write derivative works that comment on or explain it, or help with implementing it, and publish and distribute those in whole or in part, with no restrictions of any kind. The one catch is that the copyright notice and this paragraph have to stay on every copy and derivative work πŸ“œ The document itself can't be modified in any way, like removing the copyright notice or the references to the Internet Society or other Internet organizations. The exceptions are developing Internet standards, where the Internet Standards process copyright procedures must be followed, or translating it into a language other than English. These permissions are perpetual and won't be revoked by the Internet Society or its successors or assigns πŸ¦‹

This document and the information contained herein is provided on an "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

Acknowledgement

Funding for the RFC Editor function is currently provided by the Internet Society. Thank you, Internet Society 🌸

πŸ“¦ where this page comes from