github.com/Azareal/Gosora@v0.0.0-20210729070923-553e66b59003/common/gauth/authenticator.go (about) 1 // Google Authenticator 2FA 2 // Borrowed from https://github.com/tilaklodha/google-authenticator, as we can't import it as a library as it's in package main 3 package gauth 4 5 import ( 6 "bytes" 7 "crypto/hmac" 8 "crypto/sha1" 9 "encoding/base32" 10 "encoding/binary" 11 "strconv" 12 "strings" 13 "time" 14 ) 15 16 // Append extra 0s if the length of otp is less than 6 17 // If otp is "1234", it will return it as "001234" 18 func prefix0(otp string) string { 19 if len(otp) == 6 { 20 return otp 21 } 22 for i := (6 - len(otp)); i > 0; i-- { 23 otp = "0" + otp 24 } 25 return otp 26 } 27 28 func GetHOTPToken(secret string, interval int64) (string, error) { 29 secret = strings.Replace(secret, " ", "", -1) 30 31 // Converts secret to base32 Encoding. Base32 encoding desires a 32-character subset of the twenty-six letters A–Z and ten digits 0–9 32 key, err := base32.StdEncoding.DecodeString(strings.ToUpper(secret)) 33 if err != nil { 34 return "", err 35 } 36 bs := make([]byte, 8) 37 binary.BigEndian.PutUint64(bs, uint64(interval)) 38 39 // Signing the value using HMAC-SHA1 Algorithm 40 hash := hmac.New(sha1.New, key) 41 hash.Write(bs) 42 h := hash.Sum(nil) 43 44 // We're going to use a subset of the generated hash. 45 // Using the last nibble (half-byte) to choose the index to start from. 46 // This number is always appropriate as it's maximum decimal 15, the hash will have the maximum index 19 (20 bytes of SHA1) and we need 4 bytes. 47 o := (h[19] & 15) 48 49 var header uint32 50 // Get 32 bit chunk from hash starting at the o 51 r := bytes.NewReader(h[o : o+4]) 52 err = binary.Read(r, binary.BigEndian, &header) 53 if err != nil { 54 return "", err 55 } 56 57 // Ignore most significant bits as per RFC 4226. 58 // Takes division from one million to generate a remainder less than < 7 digits 59 h12 := (int(header) & 0x7fffffff) % 1000000 60 return prefix0(strconv.Itoa(int(h12))), nil 61 } 62 63 func GetTOTPToken(secret string) (string, error) { 64 // The TOTP token is just a HOTP token seeded with every 30 seconds. 65 interval := time.Now().Unix() / 30 66 return GetHOTPToken(secret, interval) 67 }