Time-based one-time password algorithm: Difference between revisions
m Add 'GenerateSecret' method |
→{{header|Go}}: Add Go |
||
Line 123: | Line 123: | ||
M4AKQBFI252H4BWO |
M4AKQBFI252H4BWO |
||
</pre> |
</pre> |
||
=={{header|Go}}== |
|||
A slightly [https://github.com/gwwfps/onetime/pull/1 fixed] version of a [https://github.com/gwwfps/onetime package by Zitao Zhang] (released under a [https://github.com/gwwfps/onetime/blob/master/LICENSE simplified BSD license]). |
|||
<lang go>// Package onetime provides a library for one-time password generation, |
|||
// implementing the HOTP and TOTP algorithms as specified by IETF RFC-4226 |
|||
// and RFC-6238. |
|||
package onetime |
|||
import ( |
|||
"crypto/hmac" |
|||
"crypto/sha1" |
|||
"encoding/binary" |
|||
"errors" |
|||
"hash" |
|||
"math" |
|||
"time" |
|||
) |
|||
// OneTimePassword stores the configuration values relevant to HOTP/TOTP calculations. |
|||
type OneTimePassword struct { |
|||
Digit int // Length of code generated |
|||
TimeStep time.Duration // Length of each time step for TOTP |
|||
BaseTime time.Time // The start time for TOTP step calculation |
|||
Hash func() hash.Hash // Hash algorithm used with HMAC |
|||
} |
|||
// HOTP returns a HOTP code with the given secret and counter. |
|||
func (otp *OneTimePassword) HOTP(secret []byte, count uint64) uint { |
|||
hs := otp.hmacSum(secret, count) |
|||
return otp.truncate(hs) |
|||
} |
|||
func (otp *OneTimePassword) hmacSum(secret []byte, count uint64) []byte { |
|||
mac := hmac.New(otp.Hash, secret) |
|||
binary.Write(mac, binary.BigEndian, count) |
|||
return mac.Sum(nil) |
|||
} |
|||
func (otp *OneTimePassword) truncate(hs []byte) uint { |
|||
sbits := dt(hs) |
|||
snum := uint(sbits[3]) | uint(sbits[2])<<8 |
|||
snum |= uint(sbits[1])<<16 | uint(sbits[0])<<24 |
|||
return snum % uint(math.Pow(10, float64(otp.Digit))) |
|||
} |
|||
// 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. |
|||
func Simple(digit int) (otp OneTimePassword, err error) { |
|||
if digit < 6 { |
|||
err = errors.New("minimum of 6 digits is required for a valid HTOP code") |
|||
return |
|||
} else if digit > 9 { |
|||
err = errors.New("HTOP code cannot be longer than 9 digits") |
|||
return |
|||
} |
|||
const step = 30 * time.Second |
|||
otp = OneTimePassword{digit, step, time.Unix(0, 0), sha1.New} |
|||
return |
|||
} |
|||
// TOTP returns a TOTP code calculated with the current time and the given secret. |
|||
func (otp *OneTimePassword) TOTP(secret []byte) uint { |
|||
return otp.HOTP(secret, otp.steps(time.Now())) |
|||
} |
|||
func (otp *OneTimePassword) steps(now time.Time) uint64 { |
|||
elapsed := now.Unix() - otp.BaseTime.Unix() |
|||
return uint64(float64(elapsed) / otp.TimeStep.Seconds()) |
|||
} |
|||
func dt(hs []byte) []byte { |
|||
offset := int(hs[len(hs)-1] & 0xf) |
|||
p := hs[offset : offset+4] |
|||
p[0] &= 0x7f |
|||
return p |
|||
}</lang> |
Revision as of 18:47, 28 September 2014
This page uses content from Wikipedia. The original article was at Time-based one-time password algorithm. The list of authors can be seen in the page history. As with Rosetta Code, the text of Wikipedia is available under the GNU FDL. (See links for details on variance) |
A Time-based One-time Password Algorithm (TOTP) is an algorithm that computes a one-time password from a shared secret key and the current time. It is the cornerstone of Initiative For Open Authentication (OATH) and is used in a number of two factor authentication systems. Essentially, both the server and the client compute the time-limited token, then the server checks if the token supplied by the client matches the locally generated token.
The task here is to implement this algorithm using 'HMAC-SHA1' and an optional step is to generate the random Base-32 string used as the secret key, but this is not a requirement. A reference implementation, based on JavaScript, can be found at the following location:
http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript
According to RFC 6238, the reference implementation is as follows:
- Generate a key, K, which is an arbitrary bytestring, and share it securely with the client.
- Agree upon an epoch, T0, and an interval, TI, which will be used to calculate the value of the counter C (defaults are the Unix epoch as T0 and 30 seconds as TI)
- Agree upon a cryptographic hash method (default is SHA-1)
- Agree upon a token length, N (default is 6)
Although RFC 6238 allows different parameters to be used, the Google implementation of the authenticator app does not support T0, TI values, hash methods and token lengths different from the default. It also expects the K secret key to be entered (or supplied in a QR code) in base-32 encoding according to RFC 3548.
- Google Authenticator App (Apple iOS)
- Google Authenticator App (Google Android)
- Microsoft Authenticator App (Windows Phone)
Caché ObjectScript
<lang cos> Class Utils.Security [ Abstract ] {
ClassMethod GetOTP(b32secret As %String) As %String { // convert base32 secret into string Set key=..B32ToStr(b32secret)
// get the unix time, divide by 30 and convert into eight-byte string Set epoch=..GetUnixTime() Set val=$Reverse($ZQChar(epoch\30))
// compute the HMAC SHA-1 hash and get the last nibble... Set hmac=$System.Encryption.HMACSHA1(val, key) Set last=$ASCII($Extract(hmac, *))
// calculate the offset and get one-time password string Set offset=$ZBoolean(last, $Char(15), 1) // logical 'AND' operation Set otpstr=$ZBoolean($Extract(hmac, offset+1, offset+4), $Char(127,255,255,255), 1)
// convert string into decimal and return last six digits Set otpdec=$ZLASCII($Reverse(otpstr)) Quit ..LeftPad(otpdec, 6) }
ClassMethod GetUnixTime() As %Integer [ Private ] { // current date and time in UTC time format Set now=$ZTimeStamp Set daydiff=(now - $ZDateH("1970-01-01", 3)) Set secs=$Piece(now, ",", 2)\1 Quit (daydiff*60*60*24)+secs }
ClassMethod LeftPad(str As %String, len As %Integer, pad As %String = 0) As %String [ Private ] { Quit $Extract($Translate($Justify(str, len), " ", pad), *-(len-1), *) }
ClassMethod ConvertBase10ToN(pNum As %Integer = "", pBase As %Integer = "", pBaseStr As %String = "", pPos As %Integer = 0) As %String [ Private ] { If pNum=0 Quit "" Set str=..ConvertBase10ToN(pNum\pBase, pBase, pBaseStr, pPos+1) Quit str_$Extract(pBaseStr, pNum#pBase+1) }
ClassMethod ConvertBaseNTo10(pStr As %String = "", pBase As %Integer = "", pBaseStr As %String = "", pPos As %Integer = 0) As %Integer [ Private ] { If pStr="" Quit 0 Set num=..ConvertBaseNTo10($Extract(pStr, 1, *-1), pBase, pBaseStr, pPos+1) Set dec=$Find(pBaseStr, $Extract(pStr, *))-2 Quit num+(dec*(pBase**pPos)) }
ClassMethod B32ToStr(b32str As %String) As %String [ Private ] { Set b32str=$ZConvert(b32str,"U") Set b32alp="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567" Set (bits,str)="" For i=1:1:$Length(b32str) { Set val=$Find(b32alp, $Extract(b32str, i))-2 Set bits=bits_..LeftPad(..ConvertBase10ToN(val, 2, "01"), 5) } For i=1:8:$Length(bits) { Set chunk=$Extract(bits, i, i+7) Set str=str_$Char(..ConvertBaseNTo10(chunk, 2, "01")) } Quit str }
ClassMethod GenerateSecret() As %String { // initialise base 32 string and alphabet Set b32str="", b32alp="ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
// build a large base 32 string For pass=1:1:4 { Set b32str=b32str_..ConvertBase10ToN($System.Encryption.GenCryptToken(), 32, b32alp) }
// return randomly generated password Quit ..LeftPad(b32str, 16) }
} </lang>
- Example:
DEMO>for i=1:1:5 write ##class(Utils.Security).GetOTP("JBSWY3DPEHPK3PXP"),! hang 15 // wait fifteen seconds 992374 992374 169898 169898 487462 DEMO>write ##class(User.View.Security).GenerateSecret() 5FWQZLQVXIBCKKMJ DEMO>write ##class(User.View.Security).GenerateSecret() M4AKQBFI252H4BWO
Go
A slightly fixed version of a package by Zitao Zhang (released under a simplified BSD license). <lang go>// Package onetime provides a library for one-time password generation, // implementing the HOTP and TOTP algorithms as specified by IETF RFC-4226 // and RFC-6238. package onetime
import ( "crypto/hmac" "crypto/sha1" "encoding/binary" "errors" "hash" "math" "time" )
// OneTimePassword stores the configuration values relevant to HOTP/TOTP calculations. type OneTimePassword struct { Digit int // Length of code generated TimeStep time.Duration // Length of each time step for TOTP BaseTime time.Time // The start time for TOTP step calculation Hash func() hash.Hash // Hash algorithm used with HMAC }
// HOTP returns a HOTP code with the given secret and counter. func (otp *OneTimePassword) HOTP(secret []byte, count uint64) uint { hs := otp.hmacSum(secret, count) return otp.truncate(hs) }
func (otp *OneTimePassword) hmacSum(secret []byte, count uint64) []byte { mac := hmac.New(otp.Hash, secret) binary.Write(mac, binary.BigEndian, count) return mac.Sum(nil) }
func (otp *OneTimePassword) truncate(hs []byte) uint { sbits := dt(hs) snum := uint(sbits[3]) | uint(sbits[2])<<8 snum |= uint(sbits[1])<<16 | uint(sbits[0])<<24 return snum % uint(math.Pow(10, float64(otp.Digit))) }
// 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. func Simple(digit int) (otp OneTimePassword, err error) { if digit < 6 { err = errors.New("minimum of 6 digits is required for a valid HTOP code") return } else if digit > 9 { err = errors.New("HTOP code cannot be longer than 9 digits") return } const step = 30 * time.Second otp = OneTimePassword{digit, step, time.Unix(0, 0), sha1.New} return }
// TOTP returns a TOTP code calculated with the current time and the given secret. func (otp *OneTimePassword) TOTP(secret []byte) uint { return otp.HOTP(secret, otp.steps(time.Now())) }
func (otp *OneTimePassword) steps(now time.Time) uint64 { elapsed := now.Unix() - otp.BaseTime.Unix() return uint64(float64(elapsed) / otp.TimeStep.Seconds()) }
func dt(hs []byte) []byte { offset := int(hs[len(hs)-1] & 0xf) p := hs[offset : offset+4] p[0] &= 0x7f return p }</lang>