This is my learning towards how to generate Time Based OTP using SHA-512 algorithm.
Created
February 18, 2021 07:05
-
-
Save yusufsyaifudin/60c0ed2638ded59ed9b27bfa77290d2d to your computer and use it in GitHub Desktop.
Time Based OTP based on https://tools.ietf.org/html/rfc6238 and https://tools.ietf.org/html/rfc4226
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package generator | |
import ( | |
"crypto/hmac" | |
"crypto/sha512" | |
"fmt" | |
"math" | |
"time" | |
) | |
// TimeCounter set current time with step counter. | |
func TimeCounter(t time.Time, timeStep int64) []byte { | |
// Divide based on step counter. | |
// For example, if we set 30 seconds, then counter will different every 30 seconds | |
counter := t.Unix() / timeStep | |
// convert counter (time based) into byte array | |
// put movingFactor value into text byte array | |
var movingFactor = make([]byte, 8) // byte[] text = new byte[8]; | |
for i := len(movingFactor) - 1; i >= 0; i-- { | |
movingFactor[i] = (byte)(counter & 0xff) | |
// The >> operator is the right shift operator. | |
// >>= is a contracted form of the right shift operator and assignment: | |
// https://stackoverflow.com/a/32933369/5489910 | |
counter = counter >> 8 | |
} | |
return movingFactor | |
} | |
// TOtp generate HMAC OTP using SHA512 based on | |
// https://tools.ietf.org/html/rfc6238 and https://tools.ietf.org/html/rfc4226#section-5.3 | |
func TOtp(counter []byte, secret string) (string, error) { | |
// Step 1: Generate an HMAC-SHA-1 value Let HS = HMAC-SHA-1(K,C) | |
// secret_buffer_size for SHA 512 is 64 bytes | |
hash := hmac.New(sha512.New, []byte(secret)) | |
hmacHash := hash.Sum(counter) | |
// Step 2: Generate a 4-byte string (Dynamic Truncation) | |
// 0xf is 15 in decimal | |
// & is a bitwise AND operator command in integer type https://golang.org/ref/spec#Arithmetic_operators | |
offset := int(hmacHash[len(hmacHash)-1] & 0xf) | |
// Step 3: Compute an HOTP value | |
// Let Snum = StToNum(Sbits) | |
// Convert S to a number in 0...2^{31}-1 | |
// Return D = Snum mod 10^Digit | |
// D is a number in the range 0...10^{Digit}-1 | |
code := (int(hmacHash[offset]&0x7f) << 24) | | |
(int(hmacHash[offset+1]&0xff) << 16) | | |
(int(hmacHash[offset+2]&0xff) << 8) | | |
int(hmacHash[offset+3]&0xff) | |
// example in RFC: int otp = binary % DIGITS_POWER[codeDigits]; | |
const digit = 4 // generate fix 4 digit length of OTP | |
code = code % int(math.Pow10(digit)) | |
// %04d will return 0001 if value = 1, 0023 if value = 23 | |
digitFormat := "%0" + fmt.Sprint(digit) + "d" | |
result := fmt.Sprintf(digitFormat, code) | |
return result, nil | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package generator | |
import ( | |
"testing" | |
"time" | |
) | |
func TestOTP(t *testing.T) { | |
var generatedOTP = make(map[string]time.Time) | |
currentTime := time.Now() | |
const timeStep = int64(30) | |
const secret = "abc" | |
for i := 0; i < 120; i++ { | |
currentTime = currentTime.Add(time.Duration(timeStep) * time.Second) | |
counter := TimeCounter(currentTime, timeStep) // every 30 second | |
otp, err := TOtp(counter, secret) | |
if err != nil { | |
t.Fatal(err) | |
} | |
if previousTime, exist := generatedOTP[otp]; exist { | |
t.Fatalf( | |
"otp %s already generated with different time but return same OTP %s (old) vs %s (new)", | |
otp, previousTime, currentTime, | |
) | |
} | |
generatedOTP[otp] = currentTime | |
} | |
for otp, generatedTime := range generatedOTP { | |
counter := TimeCounter(generatedTime, timeStep) // every 30 second | |
newOTP, err := TOtp(counter, secret) | |
if err != nil { | |
t.Fatal(err) | |
} | |
if newOTP != otp { | |
t.Fatalf("otp %s and %s should be the same because they generated in the same time %s", | |
newOTP, otp, generatedTime) | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment