1 unit HOTP;
2 
3 {HMAC-SHA1 based One-Time Password Algorithm}
4 
5 
6 interface
7 
8 (*************************************************************************
9 
10  DESCRIPTION     :  HMAC-SHA1 based One-Time Password Algorithm
11 
12  REQUIREMENTS    :  TP5-7, D1-D7/D9-D12, FPC, VP, WDOSX
13 
14  EXTERNAL DATA   :  ---
15 
16  MEMORY USAGE    :  ---
17 
18  DISPLAY MODE    :  ---
19 
20  REFERENCES      :  RFC 4226 (http://tools.ietf.org/html/rfc4226)
21                     D. M'Raihi et al: HOTP: An HMAC-Based One-Time Password Algorithm
22 
23  REMARKS         :  ---
24 
25  Version  Date      Author      Modification
26  -------  --------  -------     ------------------------------------------
27  0.10     15.03.10  W.Ehrhardt  Initial version
28  0.11     16.03.10  we          empty result if error (SHA1 not registered)
29  0.12     16.03.10  we          Version for T5-6
30  0.13     17.03.10  we          string version hotp_generate_otps
31  0.14     17.03.10  we          set/inc count procedures
32  0.15     17.03.10  we          hotp_selftest
33  0.16     20.03.10  we          code cleanup
34 ************************************************************************)
35 
36 
37 (*-------------------------------------------------------------------------
38  (C) Copyright 2010 Wolfgang Ehrhardt
39 
40  This software is provided 'as-is', without any express or implied warranty.
41  In no event will the authors be held liable for any damages arising from
42  the use of this software.
43 
44  Permission is granted to anyone to use this software for any purpose,
45  including commercial applications, and to alter it and redistribute it
46  freely, subject to the following restrictions:
47 
48  1. The origin of this software must not be misrepresented; you must not
49     claim that you wrote the original software. If you use this software in
50     a product, an acknowledgment in the product documentation would be
51     appreciated but is not required.
52 
53  2. Altered source versions must be plainly marked as such, and must not be
54     misrepresented as being the original software.
55 
56  3. This notice may not be removed or altered from any source distribution.
57 ----------------------------------------------------------------------------*)
58 
59 
60 {$i STD.INC}
61 
62 uses
63   BTypes,Hash,HMAC,SHA1;
64 
65 const
66   MinDigits  = 6;   {Minimum value for codeDigits}
67   MaxDigits  = 9;   {Maximum value for codeDigits}
68 
69 type
70   THOTPCount = packed array[0..7] of byte;  {64 bit MSB HOTP counter}
71 
72 
hotp_generate_otpnull73 function hotp_generate_otp(psecret: pointer; slen: word;
74                            {$ifdef CONST}const{$endif} movingFactor: THOTPCount;
75                            codeDigits, truncOffset: integer): str255;
76   {-Return an OTP string of codeDigits decimal digits, empty if error.    }
77   { psecret,slen: pointer to shared secret and length of secret in bytes  }
78   { movingFactor: the counter, time, etc. that changes on a per use basis.}
79   { codeDigits  : the number of decimal digits in the result string       }
80   { truncOffset : the offset into the HMAC result to start truncation. If }
81   {               not in [0..15], then dynamic truncation will be used.   }
82 
hotp_generate_otpsnull83 function hotp_generate_otps({$ifdef CONST}const{$endif} secret: str255;
84                             {$ifdef CONST}const{$endif} movingFactor: THOTPCount;
85                             codeDigits, truncOffset: integer): str255;
86   {-Return an OTP string of codeDigits decimal digits, empty if error.    }
87   { secret      : the shared secret as (ANSI) string                      }
88   { movingFactor: the counter, time, etc. that changes on a per use basis.}
89   { codeDigits  : the number of decimal digits in the result string       }
90   { truncOffset : the offset into the HMAC result to start truncation. If }
91   {               not in [0..15], then dynamic truncation will be used.   }
92 
93 
94 procedure hotp_set_count32(var cnt: THOTPCount; chi, clo: longint);
95   {-Transform the two 32 bit LSB integers to a 64 bit MSB counter}
96 
97 {$ifdef HAS_INT64}
98 procedure hotp_set_count64(var cnt: THOTPCount; cint64: int64);
99   {-Transform the 64 bit LSB integer to a 64 bit MSB counter}
100 {$endif}
101 
102 procedure hotp_inc_count(var cnt: THOTPCount);
103   {-Increment MSB counter cnt mod 2^64}
104 
hotp_selftestnull105 function  hotp_selftest: boolean;
106   {-Simple selftest of (most) hotp functions}
107 
108 
109 implementation
110 
111 
112 type
113   TBA4 = packed array[0..3] of byte;
114 
115 {---------------------------------------------------------------------------}
hotp_generate_otpnull116 function hotp_generate_otp(psecret: pointer; slen: word;
117                            {$ifdef CONST}const{$endif} movingFactor: THOTPCount;
118                            codeDigits, truncOffset: integer): str255;
119   {-Return an OTP string of codeDigits decimal digits, empty if error.    }
120   { psecret,slen: pointer to shared secret and length of secret in bytes  }
121   { movingFactor: the counter, time, etc. that changes on a per use basis.}
122   { codeDigits  : the number of decimal digits in the result string       }
123   { truncOffset : the offset into the HMAC result to start truncation. If }
124   {               not in [0..15], then dynamic truncation will be used.   }
125 var
126   ctx: THMAC_Context;
127   mac: THashDigest;
128   phash: PHashDesc;
129   i,off: integer;
130   bincode,dc: longint;
131   res: string[12];
132 const
133   pow10: array[MinDigits..MaxDigits] of longint = (1000000,10000000,100000000,1000000000);
134   dig10: array[0..9] of char8 = '0123456789';
135 begin
136   res := '';
137   phash := FindHash_by_ID(_SHA1);
138   {result will be empty if phash=nil}
139   if phash<>nil then begin
140     {Calculate HMAC(Secret, movingFactor)}
141     hmac_init(ctx, phash, psecret, slen);
142     hmac_updateXL(ctx, @movingFactor, sizeof(movingFactor));
143     hmac_final(ctx, mac);
144     {Truncation (with LSB -> MSB conversion)}
145     off := truncOffset;
146     if (off<0) or (off>15) then off := mac[19] and 15; {dynamic truncation}
147     for i:=0 to 3 do TBA4(bincode)[3-i] := mac[off+i];
148     {encode truncated HMAC}
149     if codeDigits<MinDigits then codeDigits := MinDigits;
150     if codeDigits>MaxDigits then codeDigits := MaxDigits;
151     dc := (bincode and MaxLongint) mod pow10[codeDigits];
152     for i:=1 to codeDigits do begin
153       res  := dig10[dc mod 10] + res;
154       dc := dc div 10;
155     end;
156   end;
157   hotp_generate_otp := res;
158 end;
159 
160 
161 {---------------------------------------------------------------------------}
hotp_generate_otpsnull162 function hotp_generate_otps({$ifdef CONST}const{$endif} secret: str255;
163                             {$ifdef CONST}const{$endif} movingFactor: THOTPCount;
164                             codeDigits, truncOffset: integer): str255;
165   {-Return an OTP string of codeDigits decimal digits, empty if error.    }
166   { secret      : the shared secret as (ANSI) string                      }
167   { movingFactor: the counter, time, etc. that changes on a per use basis.}
168   { codeDigits  : the number of decimal digits in the result string       }
169   { truncOffset : the offset into the HMAC result to start truncation. If }
170   {               not in [0..15], then dynamic truncation will be used.   }
171 
172 begin
173   hotp_generate_otps := hotp_generate_otp(@secret[1], length(secret), movingFactor, codeDigits, truncOffset);
174 end;
175 
176 
177 {---------------------------------------------------------------------------}
178 procedure hotp_set_count32(var cnt: THOTPCount; chi, clo: longint);
179   {-Transform the two 32 bit LSB integers to a 64 bit MSB counter}
180 begin
181   cnt[0] := TBA4(chi)[3];
182   cnt[1] := TBA4(chi)[2];
183   cnt[2] := TBA4(chi)[1];
184   cnt[3] := TBA4(chi)[0];
185   cnt[4] := TBA4(clo)[3];
186   cnt[5] := TBA4(clo)[2];
187   cnt[6] := TBA4(clo)[1];
188   cnt[7] := TBA4(clo)[0];
189 end;
190 
191 
192 {$ifdef HAS_INT64}
193 {---------------------------------------------------------------------------}
194 procedure hotp_set_count64(var cnt: THOTPCount; cint64: int64);
195   {-Transform the 64 bit LSB integer to a 64 bit MSB counter}
196 var
197   cl64: THOTPCount absolute cint64;
198 begin
199   cnt[0] := cl64[7];
200   cnt[1] := cl64[6];
201   cnt[2] := cl64[5];
202   cnt[3] := cl64[4];
203   cnt[4] := cl64[3];
204   cnt[5] := cl64[2];
205   cnt[6] := cl64[1];
206   cnt[7] := cl64[0];
207 end;
208 {$endif}
209 
210 
211 {---------------------------------------------------------------------------}
212 procedure hotp_inc_count(var cnt: THOTPCount);
213   {-Increment MSB counter cnt mod 2^64}
214 var
215   j: integer;
216 begin
217   for j:=7 downto 0 do begin
218     if cnt[j]=$FF then cnt[j] := 0
219     else begin
220       inc(cnt[j]);
221       exit;
222     end;
223   end;
224 end;
225 
226 
227 (*
228 from RFC 4226, Appendix D - HOTP Algorithm: Test Values}
229 
230 The following test data uses the ASCII string
231 "12345678901234567890" for the secret:
232 
233 Secret = 0x3132333435363738393031323334353637383930
234 
235 Table 1 details for each count, the intermediate HMAC value.
236 
237 Count    Hexadecimal HMAC-SHA-1(secret, count)
238 0        cc93cf18508d94934c64b65d8ba7667fb7cde4b0
239 1        75a48a19d4cbe100644e8ac1397eea747a2d33ab
240 2        0bacb7fa082fef30782211938bc1c5e70416ff44
241 3        66c28227d03a2d5529262ff016a1e6ef76557ece
242 4        a904c900a64b35909874b33e61c5938a8e15ed1c
243 5        a37e783d7b7233c083d4f62926c7a25f238d0316
244 6        bc9cd28561042c83f219324d3c607256c03272ae
245 7        a4fb960c0bc06e1eabb804e5b397cdc4b45596fa
246 8        1b3c89f65e6c9e883012052823443f048b4332db
247 9        1637409809a679dc698207310c8c7fc07290d9e5
248 
249                   Truncated
250 Count    Hexadecimal    Decimal        HOTP
251 0        4c93cf18       1284755224     755224
252 1        41397eea       1094287082     287082
253 2         82fef30        137359152     359152
254 3        66ef7655       1726969429     969429
255 4        61c5938a       1640338314     338314
256 5        33c083d4        868254676     254676
257 6        7256c032       1918287922     287922
258 7         4e5b397         82162583     162583
259 8        2823443f        673399871     399871
260 9        2679dc69        645520489     520489
261 *)
262 
263 {---------------------------------------------------------------------------}
hotp_selftestnull264 function hotp_selftest: boolean;
265   {-Simple selftest of (most) hotp functions}
266 const
267   secret: string[20] = '12345678901234567890';
268 var
269   count : THOTPCount;
270 begin
271   hotp_selftest := false;
272 
273   {Data from RFC 4226, Appendix D - HOTP Algorithm: Test Values}
274   fillchar(count, sizeof(count),0);
275   hotp_set_count32(count, -1, -1);
276   hotp_inc_count(count);
277   if hotp_generate_otp(@secret[1], length(secret), count, 6, -1) <> '755224' then exit;
278   hotp_inc_count(count);
279   if hotp_generate_otps(secret, count, 6, $b) <> '287082' then exit;
280   {$ifdef HAS_INT64}
281      hotp_set_count64(count, 5);
282      if hotp_generate_otps(secret, count, 6, -1) <> '254676' then exit;
283   {$endif}
284   hotp_set_count32(count, 0, 7);
285   if hotp_generate_otps(secret, count, 6, -1) <> '162583' then exit;
286 
287   {Data from Simon Josefsson HOTP Toolkit, http://www.nongnu.org/hotp-toolkit/}
288   hotp_set_count32(count, 0, 4);
289   if hotp_generate_otps(secret, count, 7, -1) <> '0338314' then exit;
290   hotp_set_count32(count, 0, 19);
291   if hotp_generate_otps(secret, count, 8, -1) <> '21578337' then exit;
292 
293   hotp_selftest := true;
294 end;
295 
296 end.
297