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  }