unit PPP; (************************************************************************* DESCRIPTION : AES PPP routines (GRC's Perfect Paper Passwords) REQUIREMENTS : TP5-7, D1-D7/D9-D10/D12, FPC, VP EXTERNAL DATA : --- MEMORY USAGE : --- DISPLAY MODE : --- REFERENCES : http://www.grc.com/ppp/algorithm.htm REMARKS : This unit provides functions for using the PPP system described on the http://www.grc.com/ppp.htm page. Although there are hints to a PPP V3 Specification, there seems to be no formal definition. IMO the best available description is from the algorithm page: "The 256-bit PPP "Sequence Key" directly provides the key for the AES-standard keyed Rijndael block cipher. A 128-bit sequence counter is initialized to zero for the first passcode, then increments once for every subsequent passcode to provide encrypted data that are translated into individual passcodes for printing on PPP passcards." Some programs linked by the GRC pages do not comply to this, and produce e.g. five standard passcodes from one 128-bit counter: Instead of '32YT 65!@' from the GRC example they give '32YT YNBq LhY# sGsm cT47 65!@ V2o6 VFjK WPFn ?aWE'. Version Date Author Modification ------- -------- ------- ------------------------------------------ 0.10 08.08.10 W.Ehrhardt Initial basic version 0.11 08.08.10 we Improved DivBlock 0.12 08.08.10 we PPP_First32, PPP_Next 0.13 08.08.10 we Map constants 0.14 08.08.10 we Error checking 0.15 09.08.10 we PPP_FirstCard, PPP_Init4Standard, PPP_Init4Extended 0.16 09.08.10 we Fix for mlen=0, PPP_SetCodesPerCard 0.17 09.08.10 we Sort character map 0.18 27.09.10 we Add $N- for TP5 **************************************************************************) (*------------------------------------------------------------------------- (C) Copyright 2010 Wolfgang Ehrhardt This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. ----------------------------------------------------------------------------*) {$i STD.INC} {$ifdef VER50} {$N-} {Once again: brain-damaged TP5 floating-point arithmetic!} {$endif} interface uses BTypes, AES_Type, AES_Base, AES_Encr; type TPPPKey = packed array[0..31] of byte; {PPP uses 256 bit keys} TPPPMap = packed array[0..255] of char8; type TPPPctx = record actx: TAESContext; map : TPPPMap; {character map} mlen: byte; {map length} clen: byte; {code length} cpc : word; {codes per card} end; const PPP_Err_non_increasing = -30; {Characters in map are not strictly increasing} PPP_Err_invalid_maplen = -31; {Invalid number of characters in map} PPP_Err_invalid_codelen = -32; {Passcode length too large or zero} const map64: string[64] = '!#%+23456789:=?@ABCDEFGHJKLMNPRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; map88: string[88] = '!"#$%&''()*+,-./23456789:;<=>?@ABCDEFGHJKLMNOPRSTUVWXYZ[\]^_abcdefghijkmnopqrstuvwxyz{|}~'; procedure PPP_Init(var pctx: TPPPctx; SeqKey: TPPPKey; smap: str255; codelen: word; var Err: integer); {-Initialize context pctx with Seqkey, character map smap, and passcode length.} { If smap is empty, all 256 characters #0..#255 are used.} procedure PPP_Init4Standard(var pctx: TPPPctx; SeqKey: TPPPKey; var Err: integer); {-Initialize context pctx with Seqkey, standard map64, passcode length = 4} procedure PPP_Init4Extended(var pctx: TPPPctx; SeqKey: TPPPKey; var Err: integer); {-Initialize context pctx with Seqkey, extended map88, passcode length = 4} procedure PPP_SetCodesPerCard(var pctx: TPPPctx; newcpc: word); {-Set new "codes per card" value, 70 if 0 } function PPP_First32(var pctx: TPPPctx; startcode: longint): str255; {-Get first PPP passcode starting with passcode startcode} function PPP_First128(var pctx: TPPPctx; start128: TAESBlock): str255; {-Get first PPP passcode for LSB 128 bit number start128} function PPP_FirstCard(var pctx: TPPPctx; cardnum: word): str255; {-Get first PPP passcode of card cardnum (use card 1 if cardnum=0)} function PPP_Next(var pctx: TPPPctx): str255; {-Return the next passcode from context pctx} implementation {---------------------------------------------------------------------------} procedure DivBlock(var a: TAESBlock; b: byte; var r: byte); {-Divide an AES LSB block by b (256 if b=0): r = a mod b; a = a div b} var i: integer; q,w: word; begin q := b; if q=0 then q := 256; {initialize "carry"} w := 0; for i:=15 downto 0 do begin {loop invariant: 0 <= w < q} w := (w shl 8) or a[i]; r := w div q; w := w mod q; a[i] := r; end; {set r to remainder, w is still < q!} r := byte(w); end; {---------------------------------------------------------------------------} procedure IncBlock(var a: TAESBlock); {-Increment an AES LSB block} var j: integer; begin for j:=0 to 15 do begin if a[j]=$FF then a[j] := 0 else begin inc(a[j]); exit; end; end; end; {---------------------------------------------------------------------------} procedure SortMap(var pctx: TPPPctx); {-Sort character map with CombSort routine} var i,j,gap,r: integer; swapped: boolean; c: char8; begin {See my CombSort routine from util archive} with pctx do begin if mlen=0 then r := 255 else r := mlen - 1; gap := r; if gap<1 then exit; repeat gap := longint(gap)*10 div 13; if (gap=9) or (gap=10) then gap := 11 else if gap<1 then gap:=1; swapped := false; for i:=0 to r-gap do begin j := i + gap; if ord(map[j]) < ord(map[i]) then begin c := map[j]; map[j] := map[i]; map[i] := c; swapped := true; end end until (gap=1) and not swapped; end; end; {---------------------------------------------------------------------------} procedure PPP_Init(var pctx: TPPPctx; SeqKey: TPPPKey; smap: str255; codelen: word; var Err: integer); {-Initialize context pctx with Seqkey, character map smap, and passcode length.} { If smap is empty, all 256 characters #0..#255 are used.} var i: integer; sorted: boolean; begin with pctx do begin Err := AES_Init_Encr(SeqKey, 256, actx); if Err<>0 then exit; fillchar(map, sizeof(map),0); if smap='' then begin {use all chars #0..#255} mlen := 0; for i:=0 to 255 do map[i] := char8(i); end else begin mlen := length(smap); if mlen=1 then begin Err := PPP_Err_invalid_maplen; exit; end; move(smap[1],map[0],mlen); sorted := true; i:= 1; while (i= ord(map[i]) then begin Err := PPP_Err_non_increasing; exit; end; end; end; end; {here 2 <= mlen <= 256 or mlen=0} if mlen=0 then i := 16 else i := trunc(128.0*ln(2.0)/ln(mlen)); if (codelen > i) or (codelen=0) then begin Err := PPP_Err_invalid_codelen; exit; end; clen := codelen; cpc := 70; {default codes pre card} end; end; {---------------------------------------------------------------------------} procedure PPP_Init4Standard(var pctx: TPPPctx; SeqKey: TPPPKey; var Err: integer); {-Initialize context pctx with Seqkey, standard map64, passcode length = 4} begin PPP_Init(pctx, SeqKey, map64, 4, Err); end; {---------------------------------------------------------------------------} procedure PPP_Init4Extended(var pctx: TPPPctx; SeqKey: TPPPKey; var Err: integer); {-Initialize context pctx with Seqkey, extended map88, passcode length = 4} begin PPP_Init(pctx, SeqKey, map88, 4, Err); end; {---------------------------------------------------------------------------} procedure PPP_SetCodesPerCard(var pctx: TPPPctx; newcpc: word); {-Set new "codes per card" value, 70 if 0 } begin if newcpc=0 then newcpc := 70; pctx.cpc := newcpc; end; {---------------------------------------------------------------------------} function PPP_Next(var pctx: TPPPctx): str255; {-Return the next passcode from context pctx} var i: integer; idx: byte; s: str255; begin s := ''; with pctx do begin AES_Encrypt(actx, actx.iv, actx.buf); for i:=1 to clen do begin DivBlock(actx.buf, mlen, idx); s := s + map[idx]; end; IncBlock(actx.IV); end; PPP_Next := s; end; {---------------------------------------------------------------------------} function PPP_First32(var pctx: TPPPctx; startcode: longint): str255; {-Get first PPP passcode starting with passcode startcode} begin with pctx do begin fillchar(actx.iv, sizeof(actx.iv), 0); TWA4(actx.iv)[0] := startcode; end; PPP_First32 := PPP_Next(pctx); end; {---------------------------------------------------------------------------} function PPP_FirstCard(var pctx: TPPPctx; cardnum: word): str255; {-Get first PPP passcode of card cardnum (use card 1 if cardnum=0)} begin if cardnum=0 then cardnum := 1; PPP_FirstCard := PPP_First32(pctx, longint(cardnum-1)*pctx.cpc); end; {---------------------------------------------------------------------------} function PPP_First128(var pctx: TPPPctx; start128: TAESBlock): str255; {-Get first PPP passcode for LSB 128 bit number start128} begin pctx.actx.iv := start128; PPP_First128 := PPP_Next(pctx); end; end.