Time-based one-time password algorithm: Difference between revisions

Content deleted Content added
Tcl implementation added
Line 478:
assert {{306281 553572 304383} eq [google 1400000000]}
}</lang>
 
=={{header|zkl}}==
Uses the MsgHash dll, which includes SHA-1, SHA-256 hashes and HMAC routines for SHA-* and MD5.
{{trans|Go}}
<lang zkl>var [const] MsgHash = Import("zklMsgHash");
// OneTimePassword stores the configuration values relevant to HOTP/TOTP calculations.
class OneTimePassword{
fcn init(Digit,TimeStep,BaseTime,HMAC){
var digit =Digit, // Length of code generated, # digits
timeStep=TimeStep, // Length of each time step for TOTP, in seconds
baseTime=BaseTime, // The start time for TOTP step calculation (seconds since Unix epoch)
hmac =HMAC; // Hash algorithm used with HMAC --> bytes
}
// hotp returns a HOTP code with the given secret and counter.
fcn hotp(secret,count){ // eg ("SOME_SECRET",123456)
hmac(secret,count.toBigEndian(8)) : // (key,msg), msg is count as 8 bytes
// --> 20 bytes (SHA1), eg (de,7c,9b,85,b8,b7,8a,a6,bc,8a,7a,36,f7,0a,90,70,1c,9d,b4,d9)
truncate(_)
}
fcn truncate(hs) // pick off bottom digit digits
{ dt(hs) % (10).pow(digit) }
fcn dt(hs){
hs[-1].bitAnd(0xf) : // bottom 4 bits (0-15) of LSB of hash to index
hs.toBigEndian(_,4).abs() // 4 bytes of hash to 32 bit int
}
// Simple returns a new OneTimePassword with the specified HTOP code length,
// SHA-1 as the HMAC hash algorithm, the Unix epoch as the base time, and
// 30 seconds as the step length.
fcn simple(digit){ //--> OneTimePassword
if(digit<6)
throw(Exception.ValueError("minimum of 6 digits is required for a valid HTOP code"));
if(digit>9)
throw(Exception.ValueError("HTOP code cannot be longer than 9 digits"));
self(digit,30,0,MsgHash.extra.hmacSHA1.fp2(False))
}
// TOTP returns a TOTP code calculated with the current time and the given secret.
fcn totp(secret){ hotp(secret, steps(Time.Clock.time())) }
fcn steps(now) { (now - baseTime)/timeStep } // elapsed time chunked
} // OneTimePassword</lang>
Note: MsgHash hashes return a string by default, they can also return the hash as bytes. Ditto the HMAC routines, it is the third parameter. So, to create a hmac that returns bytes, use (eg) MsgHash.extra.hmacSHA1.fp2(False), this creates a partial application (closure) of the hmac using SHA-1 fixing the third parameter as False.
{{out|Example use}}
<lang zkl>fcn example_simple{
// Simple 6-digit HOTP code:
secret := "SOME_SECRET";
counter := 123456;
otp := OneTimePassword.simple(6);
code := otp.hotp(secret, counter);
println(code); //-->260040 (const)
}();
 
fcn example_authenticator{
// Google authenticator style 8-digit TOTP code:
secret := "SOME_SECRET";
otp := OneTimePassword.simple(8);
code :=otp.totp(secret);
println(code) //-->eg 44653788
}();
 
fcn example_custom{
// 9-digit 5-second-step TOTP starting on midnight 2000-01-01 UTC, using SHA-256:
secret := "SOME_SECRET";
ts := 5; // seconds
t := Time.Clock.mktime(2000,1,1, 0,0,0); //(Y,M,D, h,m,s)
otp := OneTimePassword(9,ts,t,MsgHash.extra.hmacSHA256.fp2(False));
code := otp.totp(secret);
println(code) //-->eg 707355416
}();</lang>
The MsgHash HMAC routines are pretty simple, included here for completeness:
<lang zkl>// https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
fcn hmac(key,msg,hashFcn,asString){
blkSz,H,Hx:=64,hashFcn.fp1(1,False), (asString and hashFcn or H);
kn:=key.len();
if(kn>blkSz) key=H(key).howza(0);
else key=Data().fill(0,blkSz-kn).write(key).howza(0);
opad:=key.pump(Data,(0x5c).bitXor);
ipad:=key.pump(Data,(0x36).bitXor);
 
Hx(opad + H(ipad + msg)) //-->String|Data
}
fcn hmacSHA1( key,msg,asString=True){ hmac(key,msg,MsgHash.SHA1, asString) }
fcn hmacSHA256(key,msg,asString=True){ hmac(key,msg,MsgHash.SHA256,asString) }
fcn hmacMD5( key,msg,asString=True){ hmac(key,msg,Utils.MD5.calc,asString) }</lang>