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