Click Here – for the SpinRite 6.1 video walkthrough.

white spy  PPP Logo

GRC's Open, Ultra-High Security,
One Time Password System
  black spy


Code Implementation

To encourage and enable anyone who would be interested in implementing this PPP system for their own application or web service, we have published the complete Windows version of the PPP CryptoSystem in a single 15 Kbyte standalone, high-performance (100% assembly language) Windows dynamic link library (ppp.dll). We also include an accompanying 12 Kbyte Windows command-line executable (ppp.exe) which uses the ppp.dll. The executable can be used as a "shell command" by any other language environment. And, finally, as an aid to generating web page displays of PPP passcards, we provide the various HTML resources used on this site for the display of the PPP passcards:


     https://www.grc.com/files/ppp.zip     


This Windows ppp.dll may be readily called from any language and operating system platform that supports loading and calling into Windows DLLs. And, similarly, the ppp.exe can be invoked on any platform that can run Windows command-line executable programs.
The complete PPP CryptoSystem in a 15 Kbyte DLL
Please see your chosen language's documentation for information on calling Windows DLL functions. As is thoroughly documented below, the ppp.dll contains high speed assembly language implementations of the two cryptographic functions required by the PPP system, a 256-bit secure hash algorithm (SHA256) and a 256-bit keyed, 128-bit/block Rijndael/AES cipher. These are used by the main DLL library function to generate any sequence of PPP passcodes given a 256-bit "sequence key", the 0-based ordinal of the first passcode desired, and the number of passcodes requested.

The ppp.dll also includes a collection of utility functions, such as a 256-bit cryptographic random number generator for easily creating unique PPP "sequence keys", as well as functions for performing math on large 128-bit values and for converting binary passcodes into their 4-character ASCII equivalent.

By using the ppp.dll to perform the "heavy lifting" crypto operations and the various bit-manipulation operations, fully PPP-compliant highly secure one-time password authentication systems can be created with a minimum of coding and zero guesswork.

The 12 Kbyte, standalone turn-key command-line EXE
In addition to the ppp.dll file, we have also published a companion Windows command-line executable file (ppp.exe). This executable uses the ppp.dll through the dll's published API to perform standard PPP system functions and an entire working PPP authentication system could easily be created just by calling the PPP executable. You may run the command-line program in a "DOS box" command shell to see how it performs. If, for any reason, you would rather not directly invoke the PPP functions in the ppp.dll you can "shell out" to the ppp.exe to have it perform any of the main PPP functions.
The PPP.DLL Application Programming Interface (API)
The ppp.dll library is a standard Windows-style DLL compatible with 32-bit versions of Microsoft languages. It follows 'C' naming conventions and PASCAL calling conventions. As such, procedure parameters are pushed onto the stack in right-to-left order, and the called procedure is responsible for cleaning up the stack after the call to remove the caller's parameters.

Additionally . . .
  • Integer parameters are 32-bits unsigned.
  • Integer return values are 32-bits signed.
  • Pointers are 32-bit flat addressing.
  • Strings are null-terminated 8-bit ASCII.
  • Byte-ordering is Intel-style little endian.
    (Least significant bits/bytes always come first.)
The ppp.dll provides the following functions:

RetrievePasscodes
PasscodeCharBuffer:Pointer to buffer that receives retrieved passcode characters. The buffer must be at least (PasscodeCount * Passcode Length) bytes long. Successive passcode characters are stored one character per byte.
FirstPasscodeNumber:Pointer to 128-bit (16 byte) buffer specifying the 0-based ordinal of the first passcode to retrieve. This pointer can be NULL to request retrieval from the first (0th) passcode.
PasscodeCount:Integer specifying the number of passcodes to retrieve.
SequenceKey:Pointer to a 256-bit (32-byte) buffer containing the PPP "Sequence Key" of the passcodes retrieved.
CharacterSet:Pointer to a zero-terminated string containing the target PPP character set. It will be sorted into ascending ASCII order before use.
PasscodeLength:Integer specifying the number of characters per passcode.

No return value.


"RetrievePasscodes" is the ppp.dll's primary function. It fills a user-provided buffer with any number of requested passcodes, each of passcode length, occupying one byte per passcode character and with no inter-passcode padding or spacing. The passcodes are generated using the algorithm described on the "Algorithm" page. This function would typically be used to generate the data for a PPP passcard and to check a user-provided passcode. FOr example, to generate passcard 'N', given 70 passcodes per card, the 0-based first passcard number requested would be 70×(N-1).


GenerateRandomSequenceKey
SequenceKeyBuffer:Pointer to a 256-bit (32-byte) buffer which will receive a "Sequence Key" compatible with the "RetrievePasscodes" function.

No return value.


"GenerateRandomSequenceKey" fills the user-provided buffer with a highly random, unpredictable and unrepeatable 256-bit value. The value is generated by using the SHA256 cryptographic hash on a number of machine specific and extremely volatile values, including the current high-resolution time, the number of processor clock cycles since last reset, and a GUID that incorporates the machine's unique Ethernet adapter MAC address. The values returned by this function would typically be stored and associated with individual user accounts then provided to the "RetrievePasscodes" procedure as needed.


ConvertAsciiTo128BitDecimal
BinaryBuffer:Pointer to a 128-bit (16-byte) buffer which will receive the result of an ASCII to decimal conversion
AsciiString:Pointer to a null-terminated ASCII string containing only ASCII digits 0-9 and possibly commas.

Returns:1:Successful conversion with non-zero result.
0:Zero result.
-1:Invalid characters in string.
-2:Overflow - binary conversion would have required more than 128-bits.


"ConvertAsciiTo128BitDecimal" performs an ASCII to decimal conversion upon a null-terminated input string which may contain commas (which will be ignored). The 32-bit (signed) return value indicates the outcome of the operation.


AddDwordTo128bits
ValueBuffer:Pointer to a 128-bit (16-byte) buffer to which the 32-bit addend will be added.
Addend:A 32-bit value (addend) which will be added to the 128-bit "ValueBuffer."

Returns:0:Success.
-1:Result overflowed the 128-bit ValueBuffer.


"AddDwordTo128bits" performs simple math on a 128-bit value. It adds the 32-bit value provided in the "Addend" to the current value contained in the 128-bit (16-byte) buffer pointed to by "ValueBuffer". If the addition does not cause the value in the buffer to overflow, the function returns a value of '0' to indicate success. If the value overflows, '-1' is returned in the 32-bit result (all 1's). Note that in this case, the 32-bit addend value will have been added to the ValueBuffer, allowing it to "wrap around", and the function's non-zero result can be taken as a "carry" out of the ValueBuffer's most significant bit.


SubtractDwordFrom128bits
ValueBuffer:Pointer to a 128-bit (16-byte) buffer from which the 32-bit subtrahend will be subtracted.
Subtrahend:A 32-bit value (subtrahend) which will be subtracted from the 128-bit "ValueBuffer."

Returns:0:Success.
-1:Result underflowed the 128-bit ValueBuffer.


"SubtractDwordFrom128bits" performs simple math on a 128-bit value. It subtracts the 32-bit value provided in the "Subtrahend" from the current value contained in the 128-bit (16-byte) buffer pointed to by "ValueBuffer". If the subtraction does not cause the value in the buffer to underflow, the function returns a value of '0' to indicate success. If the value underflows, '-1' is returned in the 32-bit result (all 1's). Note that in this case, the 32-bit subtrahend value will have been subtracted from the ValueBuffer, allowing it to "wrap around", and the function's non-zero result can be taken as a "borrow" from the ValueBuffer's most significant bit.


Mult128bitsByDword
ValueBuffer:Pointer to a 128-bit (16-byte) buffer which will be multiplied by the value in the 32-bit multiplier.
Multiplier:A 32-bit value (multiplier) which will be multiplied by the value in the 128-bit "ValueBuffer."

Returns:0:Success.
-1:Result overflowed the 128-bit ValueBuffer.


"Mult128bitsByDword" performs simple math on a 128-bit value. It multiplies the 32-bit value provided in the "Multiplier" by the current value contained in the 128-bit (16-byte) buffer pointed to by "ValueBuffer". If the multiplication does not cause the value in the buffer to overflow, the function returns a value of '0' to indicate success. If the value overflows, '-1' is returned in the 32-bit result (all 1's). Note that in this case, the 32-bit multiplier value will have been multiplied by the ValueBuffer, and the ValueBuffer will contain the least significant 128-bits of the result.


Divide128bitsByDword
ValueBuffer:Pointer to a 128-bit dividend (16-byte) buffer which will be divided by the value in the 32-bit divisor.
Divisor:A 32-bit value (divisor) by which the 128-bit dividend will be divided.

Returns::32-bit remainder from division.


"Divide128bitsByDword" performs simple math on a 128-bit value. It divides the 128-bit buffer pointed to by "ValueBuffer" by the 32-bit value provided in the "Divisor". The remainder of the division operation returned by the function.
The complete PPP CryptoSystem Command-Line EXE
When the ppp.exe is executed without any parameters it produces the following self-description:

ppp.exeAn implementation of the Perfect Paper Passwords CryptoSystem.
(c) copyright 2007 and released as freeware for the benefit of the
Internet community by Gibson Research Corporation. See GRC's
PPP webpages https://www.GRC.com/ppp.htm for docs.

Usage: ppp ["]secret passphrase["] [starting_passcode [passcode_count]]
secret passphrase: Either any string with optional delimiting quotes, which will be SHA-256 hashed into a 256-bit PPP sequence key, *OR* exactly 64 non-quoted all-HEX lowercase characters (0-9 a-f) which will be taken as the 256-bit PPP sequence key (without hashing). *OR* a null string "" which will generate and return a cryptographically random 256-bit value suitable for use as a permanent account's PPP sequence key.
starting_passcode: The first passcode in the sequence to return. Passcodes are numbered from 0. If this OPTIONAL parameter is not provided, PPP will perform a SHA-256 hash on the secret passphrase and dump the 64-character HEX result for subsequent use.
passcode_count: The number of sequential passcodes to return. If this OPTIONAL parameter is not provided, PPP will default its value to '1' to generate a single passcode.

The three arguments are space-delimited. If the secret_passphrase contains embedded spaces it must be enclosed in quotes and any embedded quotes must be doubled to count as a single non-delimiting quote.


This ppp.exe command-line utility requires the use of the ppp.dll for the implementation of the PPP CryptoSystem. Therefore the ppp.dll must either reside in the same directory as ppp.exe (where Windows looks for it first), or be located somewhere on the executing process's execution path (typically the \Windows or \Windows\System32 directories.)

The ppp.exe was designed to be easy to use during experimentation from the command-line, and also when invoked as a "shell process" by any other language. The following samples will demonstrate the use of the ppp.exe for various purposes:

Generate a PPP Sequence Key
Input:ppp ""
Sample output:53303f97ddcf91ed74391fc5c3661246
32427e1c93c1a2e2836d006fa2653dc1

Note: The output hex string is a single unbroken string of 64 characters without line feeds or carriage returns. The output is shown broken into three lines above for web page display convenience only.


Passing the ppp.exe the single two-character string ("") parameter instructs it to generate a cryptographically random 256-bit value appropriate for use as a PPP Sequence Key. Internally, this simply calls the ppp.dll's "GenerateRandomSequenceKey" function, then converts the returned 32-byte buffer into an output of 64 hex characters. The 64-character hex string returned can be provided to the ppp.exe for subsequent keyed operations, as follows:


Generate 70 passcodes for the first passcard
Input:ppp53303f97ddcf91ed74391fc5c3661246
32427e1c93c1a2e2836d006fa2653dc1 0 70
Output:76wr wnbP kFw3 SgSK Ar8@ 5un2 W!ca
u!g3 y=Pf =Vn: c7Rh snV% Hyz9 @5D4
Ha@= R6x4 SJaD dW6# pjhw TCM9 hgic
MK%# yf66 Twft RzML iA!t #3NW hVkA
ddAA ox8x D+dN v?aa mSvB 7H?F r#La
mVFp Us15d4cbc c=XV 3Jn: oj7d 95ub ?dye
YXoF a2mK bF!p Bvpj +Jkq ypKA VXdy
?XjU rmZc ErYN X5PD xG5+ ZuRk FDTV
dBcA tMsn zpyL RtNi kvrs wWxe veyn
JroN UK 5eVG FpfR !cwX Afwg tn3w

Note: Once again, the input hex string shown above is shown on three lines for display convenience. It should be provided as a single unbroken string of 64 lowercase hex characters without line feeds or carriage returns. The output is shown broken into ten lines of seven passcodes, as they would be displayed on a 7x10 passcard. But the function actually outputs them in a single unbroken stream with each passcode separated by a single space.

The two arguments — zero (0) and seventy (70) — shown following the hex sequence key request a series of 70 passcodes beginning with the sequence's first passcode (numbered from 0).


Commands of this form would be used to generate sets of sequential passcodes for the creation of individual passcards for transmission to the user and for their subsequent printing. The example above would print the first 70 passcodes (0-69) with the second and third arguments being "0 70". To print the second passcard's code, the arguments would be "70 70". And "140 70" would be used to generate passcodes for the third passcard.


Generate one specific passcode to test a user's response
Input:ppp53303f97ddcf91ed74391fc5c3661246
32427e1c93c1a2e2836d006fa2653dc1 2 1
Output:kFw3

Note: As before, the input hex string shown above is shown on three lines for display convenience. It should be provided as a single unbroken string of 64 lowercase hex characters without line feeds or carriage returns.

The two arguments — two (2) and one (1) — shown following the hex sequence key, request the third passcode for the series created with the provided sequence key. (Remember that passcode ordinals are 0-based with the first passcode numbered zero "0".) If you look at the 70-passcode example above, you will see that the returned passcode of "PaRp" was the third passcode returned in the block of 70 for the same sequence key.

Note also that for the case of requesting a single passcode, providing the third argument of one (1) was optional. If not provided, a single passcode is returned by default.


A command of this form can be used to generate the next expected passcode in the series for a given sequence key. It would typically be used to authenticate a user by checking their response to a "provide next passcode" challenge.

Perfect Paper Password Pages:

Jump to top of page
Gibson Research Corporation is owned and operated by Steve Gibson.  The contents
of this page are Copyright (c) 2024 Gibson Research Corporation. SpinRite, ShieldsUP,
NanoProbe, and any other indicated trademarks are registered trademarks of Gibson
Research Corporation, Laguna Hills, CA, USA. GRC's web and customer privacy policy.
Jump to top of page

Last Edit: May 04, 2013 at 18:12 (4,375.55 days ago)Viewed 3 times per day
close