GithubHelp home page GithubHelp logo

otp's Introduction

otp: One Time Password utilities Go / Golang

PkgGoDev Build Status

Why One Time Passwords?

One Time Passwords (OTPs) are an mechanism to improve security over passwords alone. When a Time-based OTP (TOTP) is stored on a user's phone, and combined with something the user knows (Password), you have an easy on-ramp to Multi-factor authentication without adding a dependency on a SMS provider. This Password and TOTP combination is used by many popular websites including Google, GitHub, Facebook, Salesforce and many others.

The otp library enables you to easily add TOTPs to your own application, increasing your user's security against mass-password breaches and malware.

Because TOTP is standardized and widely deployed, there are many mobile clients and software implementations.

otp Supports:

  • Generating QR Code images for easy user enrollment.
  • Time-based One-time Password Algorithm (TOTP) (RFC 6238): Time based OTP, the most commonly used method.
  • HMAC-based One-time Password Algorithm (HOTP) (RFC 4226): Counter based OTP, which TOTP is based upon.
  • Generation and Validation of codes for either algorithm.

Implementing TOTP in your application:

User Enrollment

For an example of a working enrollment work flow, GitHub has documented theirs, but the basics are:

  1. Generate new TOTP Key for a User. key,_ := totp.Generate(...).
  2. Display the Key's Secret and QR-Code for the User. key.Secret() and key.Image(...).
  3. Test that the user can successfully use their TOTP. totp.Validate(...).
  4. Store TOTP Secret for the User in your backend. key.Secret()
  5. Provide the user with "recovery codes". (See Recovery Codes bellow)

Code Generation

  • In either TOTP or HOTP cases, use the GenerateCode function and a counter or time.Time struct to generate a valid code compatible with most implementations.
  • For uncommon or custom settings, or to catch unlikely errors, use GenerateCodeCustom in either module.

Validation

  1. Prompt and validate User's password as normal.
  2. If the user has TOTP enabled, prompt for TOTP passcode.
  3. Retrieve the User's TOTP Secret from your backend.
  4. Validate the user's passcode. totp.Validate(...)

Recovery Codes

When a user loses access to their TOTP device, they would no longer have access to their account. Because TOTPs are often configured on mobile devices that can be lost, stolen or damaged, this is a common problem. For this reason many providers give their users "backup codes" or "recovery codes". These are a set of one time use codes that can be used instead of the TOTP. These can simply be randomly generated strings that you store in your backend. Github's documentation provides an overview of the user experience.

Improvements, bugs, adding feature, etc:

Please open issues in Github for ideas, bugs, and general thoughts. Pull requests are of course preferred :)

License

otp is licensed under the Apache License, Version 2.0

otp's People

Contributors

ben-rowe avatar cathalgarvey avatar champkeh avatar clarfonthey avatar delicb avatar dominikschulz avatar erfan-khadem avatar jongillham avatar juunini avatar kszafran avatar maxvw avatar mvineetmenon avatar phylake avatar pquerna avatar reginaldosousa avatar roj1512 avatar shawnps avatar spring1843 avatar srinivas32 avatar svbnbyrk avatar zemirco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

otp's Issues

Prevent re-use of TOTP code

Hey,

just wondering if the library supports passing the last_used timestamp time.Time to the TOTP validation. Since these are "one-time" codes its really important that each code can be used only one time otherwise an attacker would be able to login with the same token (if stolen) on the account.

I was using a Ruby backend before and there was a option for it:
https://github.com/mdp/rotp/blob/62874be71d74380d252c73409dd81da08b021497/lib/rotp/totp.rb#L39

Is there any way to archive this with this project?

Thanks in advance!

Alex

Must use AlgorithmSHA1 with iOS version of Google Authenticator

        key,_ := totp.Generate(totp.GenerateOpts{ 
              AccountName: "XYZ Test", 
              Issuer: "some.domain.com", 
              Algorithm: otp.AlgorithmSHA512, 
        })
        
        var buff bytes.Buffer
	img, _ := key.Image(256,256)
	f, _ := os.Create("test.html")
	png.Encode(&buff, img)
	encodedString := base64.StdEncoding.EncodeToString(buff.Bytes())
	htmlImage := "<img src=\"data:image/png;base64," + encodedString + "\" />"
	l, err := f.WriteString(htmlImage)
	if err != nil {
		log.Println(err)
	}

The resultant test.html shows a QR code that provides correct codes when scanned with Google Authenticator on Android, but not on iPhone.

validate six digits one time password

First of all thank you for this great library!

I'm using otp for my web service and I'm currently trying to write some tests against my login process. Since otp uses six digits by default (same as google authenticator app) I have to create a valid otp with six digits.

I looked at your tests for otp but they all use eight digits. How can I generate a valid six digit one time password?

Thank you!

Problems with TOTP SHA512

Hi,
I use the following code (which is very similar to this example, if not for line 19 (which I added), line 38 (which I added), and the cycle (lines 56 and 64) which I added to simplify testing with multiple clients:

package main

import (
	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"

	"bufio"
	"bytes"
	"fmt"
	"image/png"
	"io/ioutil"
	"os"
)

func display(key *otp.Key, data []byte) {
	fmt.Printf("Issuer:       %s\n", key.Issuer())
	fmt.Printf("Account Name: %s\n", key.AccountName())
	fmt.Printf("Secret:       %s\n", key.Secret())
	fmt.Printf("URL:          %s\n", key.URL())
	fmt.Println("Writing PNG to qr-code.png....")
	ioutil.WriteFile("qr-code.png", data, 0644)
	fmt.Println("")
	fmt.Println("Please add your TOTP to your OTP Application now!")
	fmt.Println("")
}

func promptForPasscode() string {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Enter Passcode: ")
	text, _ := reader.ReadString('\n')
	return text
}

func main() {
	key, err := totp.Generate(totp.GenerateOpts{
		Issuer:      "Example.com",
		AccountName: "[email protected]",
		Algorithm:   otp.AlgorithmSHA512,
	})
	if err != nil {
		panic(err)
	}
	// Convert TOTP key into a PNG
	var buf bytes.Buffer
	img, err := key.Image(200, 200)
	if err != nil {
		panic(err)
	}
	png.Encode(&buf, img)

	// display the QR code to the user.
	display(key, buf.Bytes())

	// Now Validate that the user's successfully added the passcode.
	fmt.Println("Validating TOTP...")
	for {
		passcode := promptForPasscode()
		valid := totp.Validate(passcode, key.Secret())
		if valid {
			println("Valid passcode!")
		} else {
			println("Invalid passocde!")
		}
	}
}

In a test run, this is what it outputs:

Issuer:       Example.com
Account Name: [email protected]
Secret:       N3K5NHE26HHXP2CL
URL:          otpauth://totp/Example.com:[email protected]?algorithm=SHA512&digits=6&issuer=Example.com&period=30&secret=N3K5NHE26HHXP2CL

qr-code

I tried to add the QRCode to Google Authenticator (on Android), FreeOTP (on Android), Google Authenticator (on iOS), FreeOTP (on iOS), gopass (on Linux).

On multiple tests, FreeOTP (on both Android and iOS), Google Authenticator on iOS and gopass agree on the generated OTPs, while Google Authenticator on Android does not.
Only the code from Google Authenticator on Android is recognised as right.

Now, I'm not sure if this is a problem on the server side or on the client side, so I'm opening the ticket on both the clients (FreeOTP Android, gopass) and the server (github.com/pquerna/otp)

invalid code

image
It shows "Invalid 2FA Code" after first time Enter in Alist login, but second time Enter will be OK.

What is pascode exactly ?

In you´r example code I don´t undestand what is it pascode, what I have to introduce to pass the validate ?

Apache license vs NOTICE file

Hi,

From the LICENSE/NOTICE file it's clear that this project is under the Apache license. But there are some difficulties preventing me from attributing my use as required under this license.

  • The LICENSE file is a verbatim copy of the Apache license. At the bottom of the text, there's a section APPENDIX: How to apply the Apache License to your work. which has not been followed.
  • The NOTICE file contains copyright information without a license grant.

Please, remove the NOTICE file, and replace the LICENSE file with simply the following:

Copyright 2014, Paul Querna <http://paul.querna.org/>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Regards
mappu

URL Encoding bug

Hello

if the issuer contains a space, the URL encoder will mix up the URL.

key, err := totp.Generate(totp.GenerateOpts{
	Issuer:      "Test Issuer",
	AccountName: "[email protected]",
})

Actual result:
otpauth://totp/Test%20Issuer:[email protected]?algorithm=SHA1&digits=6&issuer=Test+Issuer&period=30&secret=QE2C7JXZB3TY3FBKL6PB7PZXP7UCRPOA

otp_issue

Expected result:
otpauth://totp/Test%20Issuer:[email protected]?algorithm=SHA1&digits=6&issuer=Test%20Issuer&period=30&secret=QE2C7JXZB3TY3FBKL6PB7PZXP7UCRPOA

otp_issue_expected

Missing padding is not re-added

Some services like Dropbox, Amazon, Streamtip, Nightbot, … are issuing TOTP secrets whose padding is missing. These TOTP URLs are recognised perfectly fine by Authy or Google Authenticator while this library throws an error: Decoding of secret as base32 failed.

Even though this is indeed a broken base32 string it can be fixed very fast and this should be implemented in the library itself to ensure it can use the same secrets as for example Google Authenticator.

Testcase:
download
otpauth://totp/Example?secret=JBSWY3DPEHPK3PX

Required code to fix the missing padding:
https://play.golang.org/p/5R3qUOTO2a

Generated custom code sometimes comes as 5 digits long instead of 6

passcode, err := totp.GenerateCodeCustom(secret, time.Now(), totp.ValidateOpts{
      Period:    otpTTL,
      Skew:      1,
      Digits:    otp.DigitsSix,
      Algorithm: otp.AlgorithmSHA512,
})

if err != nil {
   return err
}

The code sometimes generates a 5 digits long passcode instead of 6 as set with the otp.DigitsSix parameter. And it happens randomly

go mod pulls the old version of the package

Since the latest commit be78767 on the master branch doesn't have a tag, go mod pulls the commit b7b8925 (v1.0.0, 9 commits behind). New users of this package, especially those who are using go modules, need to explicitly run go get github.com/pquerna/otp@master.

Mac M1/M2 bug on uint64 representation of negative values

On Mac M1/M2, otp validation fails for dates in any year before 1970. This can be associated to this bug golang/go#62725 as uint64 cannot represent negative values and converting floats to uint64s is implementation defined; which is related to the float to uint64 conversion in totp.ValidateCustom:

counter := uint64(math.Floor(float64(t.Unix()) / float64(opts.Period)))

Example 1

Passes for any year from 1970

package main

import (
	"log"
	"time"

	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
)

func main() {
	t := time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)
	secretKey := "5F5FKHNJPUEP5QXIBIRQZHTY4JJOO3GL"

	passcode, err := totp.GenerateCodeCustom(secretKey, t, newMFAValidationOpts())
	if err != nil {
		log.Fatal(err)
	}

	valid, err := totp.ValidateCustom(passcode, secretKey, t, newMFAValidationOpts())
	if err != nil {
		log.Fatal(err)
	}

	log.Println(valid)
}

func newMFAValidationOpts() totp.ValidateOpts {
	return totp.ValidateOpts{
		Period:    30,
		Skew:      1,
		Digits:    6,
		Algorithm: otp.AlgorithmSHA1,
	}
}

Output

 $ go run ./totp
2023/09/21 12:12:13 true

Example 2

Fails for any year before 1970

package main

import (
	"log"
	"time"

	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
)

func main() {
	t := time.Time{}
	secretKey := "5F5FKHNJPUEP5QXIBIRQZHTY4JJOO3GL"

	passcode, err := totp.GenerateCodeCustom(secretKey, t, newMFAValidationOpts())
	if err != nil {
		log.Fatal(err)
	}

	valid, err := totp.ValidateCustom(passcode, secretKey, t, newMFAValidationOpts())
	if err != nil {
		log.Fatal(err)
	}

	log.Println(valid)
}

func newMFAValidationOpts() totp.ValidateOpts {
	return totp.ValidateOpts{
		Period:    30,
		Skew:      1,
		Digits:    6,
		Algorithm: otp.AlgorithmSHA1,
	}
}

Output

$ go run ./totp
2023/09/21 12:14:58 false

Google changed provisioning of totp-URLs to lowercase-base32 now, which fails to parse

Google gave me a QR code with this content: otpauth://totp/Google%3Afoo%40example.com?secret=qlt6vmy6svfx4bt4rpmisaiyol6hihca&issuer=Google (don't worry, I haven't taken this secret into use).

It yields this error: Decoding of secret as base32 failed

If I uppercase the secret myself (=> otpauth://totp/Google%3Afoo%40example.com?secret=QLT6VMY6SVFX4BT4RPMISAIYOL6HIHCA&issuer=Google), it succeeds.

can I use a secret string containg specail characters?

I want to generate a password so I used the function in totp package GenerateCodeCustom() as

   func getPassword(token_shared_secret string) string{
	opts := totp.ValidateOpts{}
	opts.Period = 30
	opts.Skew = 1
	opts.Digits = 10
	opts.Algorithm = otp.AlgorithmSHA512
	
	var password string
	t := time.Now()
	password, err := totp.GenerateCodeCustom(token_shared_secret,t,opts)
	if err != nil {
		panic(err)
	}
	return password
}

var password string = getPassword(myID+subfix)

I needed to change some options like that cuz the main purpose is to make a HTTP POST to some URL and they directed me to make the password as an 10-digit time-based one, time step X is 30secs ,T0 is 0 and use HMAC-SHA-512 for the hash function.

the parameter I put as myID+subfix is a concatenated string with an email address and an English string.

The problem is that email address contains special charters @ and . (at and dot). And I failed to get the password with the error sign

panic: Decoding of secret as base32 failed.

So I tried modifying the GenerateCodeCustom() function in hotp package
I changed the line (line no.84)

secretBytes, err := base32.StdEncoding.DecodeString(secret)

as

secretBytes := []byte(secret)

At least I succeeded to get a ten-digit time-based password but I don't think it is correct one because the server keep sending me http 401 response. I think it's not a proper way, I may need to do something.

Can you let me know how can I solve this ?

Hiding Code Generation Makes Testing Hard

Hey @pquerna, thanks for this library! I'm currently reimplementing a security system from Python in Go that uses TOTP to control access to a physical location, and it's great to have a sane interface for validating codes.

The problem though, is that this library is only for server-side authentication and not for client-side authcode generation. This seems like a missed opportunity generally, but specifically it makes testing very awkward. I'd like to be able to generate a few keys for testing, then run my system through its paces using these keys, but the interface expects that external code will do the code generation.

Any way you could add a pair of generator methods to totp.Key, such as CodeFromCounter(i int) string and CodeFromTime(t time.Time) string, for testing and client generation uses?

Thanks!

url ending in newline no longer work

I think the #10 fix made urls ending in newline no longer function, so I started getting crashes when I updated to the most recent otp. For backwards compatibility, it might be nice to throw in a strings.TrimWhite() around the received url before parsing it.

package main

import (
	"fmt"
	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"
	"time"
)

func main() {
	w, err := otp.NewKeyFromURL(`otpauth://totp/Example:[email protected]?secret=JBSWY3DPEHPK3PXP
`)
	panicOn(err)
	sec := w.Secret()
	fmt.Printf("sec = '%v'\n", sec)
	code, err := totp.GenerateCode(sec, time.Now())
	panicOn(err)
	fmt.Printf("code = '%#v'\n", code)
}

func panicOn(err error) {
	if err != nil {
		panic(err)
	}
}

result

Compilation started at Sun Aug  6 17:08:27

go run min.go
sec = 'JBSWY3DPEHPK3PXP
'
panic: Decoding of secret as base32 failed.

goroutine 1 [running]:
main.panicOn(0x11581a0, 0xc42000e5f0)
    /Users/jaten/min.go:23 +0x4a
main.main()
    /Users/jaten/min.go:17 +0x187
exit status 2

Compilation exited abnormally with code 1 at Sun Aug  6 17:08:27

Prevent Conflicting Accounts

Google proposes actions mentioned on Conflicting Accounts to prevent conflicting accounts with same username on different websites.

I come accross it using Gitea in production and in my local machine for development.
While testing TOTP on my local machine I unfortunately have overwritten my key from production server, thus locking me out of the production system.

Must admit that I am new to TOTP protocol.
Is it something you can implement in your library or is it the reponsibility of the library's user?

OTP verification failed when time delta is less than period

I'm not able to validate a TOTP even when time delta is less than period specified, lets say the OTP was generated on x timestamp then validating OTP on x+200 timestamp is failing even when period specified is 300.

The issue is not reproducible at go playground, there the validation is working fine, here's the go playground link : https://go.dev/play/p/xv8TM8pKlq6
This is the go version I'm using at my machine

➜  temp git:(main) ✗ go version  
go version go1.21.3 darwin/amd64

library version

github.com/pquerna/otp v1.4.0

logs from my machine

➜  temp git:(main) ✗ go run main.go
2023/11/09 07:48:34 otp generated 633653
2023/11/09 07:48:34 2023-11-09 07:48:34.344927 +0530 IST m=+0.000137523
2023/11/09 07:48:34 2023-11-09 07:51:54.344927 +0530 IST m=+200.000137523
2023/11/09 07:48:34 otp verification failed

logs from go-playground

2009/11/10 23:00:00 otp generated 902988
2009/11/10 23:00:00 2009-11-10 23:00:00 +0000 UTC m=+0.000000001
2009/11/10 23:00:00 2009-11-10 23:03:20 +0000 UTC m=+200.000000001
2009/11/10 23:00:00 otp verified

Program exited.

Regeneration of a key not possible

🔍 Problem Description:

I am encountering a problem where I cannot recover the QR code after restarting my application. The key.Secret() is the only information that is permanently stored. However, it seems impossible to recover the same key using only the secret.

💡 Relevant Code Snippet:

In the code section below from totp.go (lines 182-192), I initially believed that the key could be recovered only from the secret.

v := url.Values{}
if len(opts.Secret) != 0 {
    v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))
} else {
    secret := make([]byte, opts.SecretSize)
    _, err := opts.Rand.Read(secret)
    if err != nil {
        return nil, err
    }
    v.Set("secret", b32NoPadding.EncodeToString(secret))
}

However, recent tests have contradicted this assumption.

🔬 Test Results:

fmt.Println(keySecret)
key, err := totp.Generate(totp.GenerateOpts{
    Issuer:      issuer,
    AccountName: accountName,
    Secret:      []byte(keySecret),
})
fmt.Println(key.Secret())

keySecret: JRKDOQRSJJBDIVCRLJJEQS22KBGQINGFQMRUGZHDGNKYINBUSRKI
key.Secret(): KKKJFUIT2RKJJUUSSCIREVMQ2SJRFEURKRKMZDES2CI5IQJFHEORSRJVJFKR22JBCEOTSLLFEU4QSVKNJEWS

🛠️ Environment:

Operating System: Windows X86@64
Golang Version: 1.21
Library version used: pqerna/otp v1.4.0

🏁 Expected Outcome:

My expectation is to get a key with the exact same secret when entering the secret.

🚫 Actual Outcome:

Because the entered secret is encoded again, another secret is created.

v.Set("secret", b32NoPadding.EncodeToString(opts.Secret))

📝 Solution:

I assume that this decision was made on purpose so I provide a wrapper that decrypts the entered secret first.

func Regenerate(opts GenerateOpts) (*otp.Key, error) {
	if opts.SecretSize == 0 {
		return nil, otp.ErrRegenerateMissingSecret
	}
	secret := make([]byte, base32.StdEncoding.DecodedLen(len(opts.Secret)))
	_, err := base32.StdEncoding.Decode(secret, opts.Secret)
	if err != nil {
		return nil, err
	}
	opts.Secret = secret
	return Generate(opts)
}

No supprot for secret in GenerateOpts?

Hi,
why is there no direct input for GenerateOpts? The Generate() gives me key with full information and QR code generation, but I cannot set secret so I have to use GenerateCodeCustom() and parse it afterwards. Why is that? I am creating users whose IDs are determined after the yare saved into storage so I can issue totp keys onyl after the fact but I need to store the secret while the yare being created so I cannot use secret generated by Generate() method :(

Problems with example code and google authenticator (android)

Hello,
So I was trying out the library and using google authenticator to scan the QR codes. This is the code I'm using to do so.

import (
	"github.com/pquerna/otp"
	"github.com/pquerna/otp/totp"

	"bufio"
	"bytes"
	"fmt"
	"image/png"
	"io/ioutil"
	"os"
)

func display(key *otp.Key, data []byte) {
	fmt.Printf("Issuer:       %s\n", key.Issuer())
	fmt.Printf("Account Name: %s\n", key.AccountName())
	fmt.Printf("Secret:       %s\n", key.Secret())
	fmt.Printf("URL:          %s\n", key.URL())
	fmt.Println("Writing PNG to qr-code.png....")
	ioutil.WriteFile("qr-code.png", data, 0644)
	fmt.Println("")
	fmt.Println("Please add your TOTP to your OTP Application now!")
	fmt.Println("")
}

func promptForPasscode() string {
	reader := bufio.NewReader(os.Stdin)
	fmt.Print("Enter Passcode: ")
	text, _ := reader.ReadString('\n')
	return text
}

func main() {
	key, err := totp.Generate(totp.GenerateOpts{
		Issuer:      "Example.com",
		AccountName: "[email protected]",
		Algorithm:   otp.AlgorithmSHA1,
	})
	if err != nil {
		panic(err)
	}
	// Convert TOTP key into a PNG
	var buf bytes.Buffer
	img, err := key.Image(200, 200)
	if err != nil {
		panic(err)
	}
	png.Encode(&buf, img)

	// display the QR code to the user.
	display(key, buf.Bytes())

	// Now Validate that the user's successfully added the passcode.
	fmt.Println("Validating TOTP...")
	for {
		passcode := promptForPasscode()
		valid := totp.Validate(passcode, key.Secret())
		if valid {
			println("Valid passcode!")
		} else {
			println("Invalid passocde!")
		}
	}
}

This generates a QR code which I am able to scan with goole authenticator, but everytime i try the passcode it is invalid. Wanted to know if theres some issue with my code.

Invalid codes (example)

Hello,

I am encountering a issue which was first flagged by another user last year. The example file runs fine, but the codes generated by the apps are all invalid when verified. However, this does not happen on every system I tested - three of our laptops have this issue, while a fourth runs the example and the codes are validated correctly. This is a really weird issue, and we are seeing it with some of the other Golang OTP, so it might be related to the OTP generation / validation process itself or to the system date/time.

Example on how to handle recovery codes?

Forgive me if this is a stupid question, how exactly can I handle recovery codes for users?

In README, you said "These can simply be randomly generated strings that you store in your backend" but I could not find the code that do this.

Does that mean I need to handle recovery codes myself? I was thinking along the lines of

  1. Generate recovery codes in backend
  2. Give users recovery codes
  3. Compare passcode with recovery codes (pulled from backend storage). If not matched, then compare passcode with TOTP / HOTP as usual? If matched, remove recovery codes (one time use like Github)

Let me know if you have plan to support this behavior natively in this package, or if you are interested in a Pull Request that does this (backend storage via an interface, of course)?

Invalid URL for secret size not divisible by 5

When I try to generate totp QR code with secret size that is not divisible by 5 I get error as in attached images.

Cause of it is that base32 that is used to encode secret adds padding and uses character "=" whenever length of payload to be encoded is not divisible by 5. This can be seen here: https://play.golang.org/p/Uu-iQQYsqr

URL that is generated is escaped and "=" turns into "%3D" which is not valid when it comes to Google authenticator and Authy (I have not tried with other apps).

Example of generated URL is:

otpauth://totp/MyIssuer:myUsername?algorithm=SHA512&digits=6&issuer=MyIssuer&period=30&secret=RAAE72RC75YKBVGZ2C35C%3D%3D%3D

RFC3548 states that payload should be encoded unless the specification referring to
it document explicitly states otherwise. And in Google authenticator document
about Key URI format it is stated that padding is not required and should be omitted.

I have only used TOTP, but HOTP has same issue (as quick test reviles).

Google Authenticator
Authy

How to fix QR image size

It looks like QR image size depends on accountName length.

I was wondering how to fix QR image size.

OTP is validating where it should not Validate.

Hello,
Hope you are fine.
My issue is when i generate the totp it stays valid for more then one otp.
For Example
1st otp is 345
2nd otp is 456
3rd otp is 567
When first otp(345) generated and i enter it. It says its valid (ok no problem here).
When 2nd otp(456) generated and i enter the 1st otp(345). It also says its a valid (problem).
The first otp(345) become invalid when 3rd otp(567) is generated.
Why is that? Am i doing something wrong? What should i Do?

CODE
func main() {
key, err := totp.Generate(totp.GenerateOpts{
Issuer: "Example.com",
AccountName: "[email protected]",
Secret: []byte("YESTHISSHOULDBEVSLIDJL"),
})
if err != nil {
panic(err)
}
// Convert TOTP key into a PNG
var buf bytes.Buffer
img, err := key.Image(200, 200)
if err != nil {
panic(err)
}
png.Encode(&buf, img)

// display the QR code to the user.
display(key, buf.Bytes())

// Now Validate that the user's successfully added the passcode.
fmt.Println("Validating TOTP...")
fmt.Println("Printing TOTP Key:\t", key)
fmt.Println()

// in this loop you can check the scenario i described above.
for {
passcode := promptForPasscode()
valid := totp.Validate(passcode, key.Secret())
if valid {
fmt.Println("Code is Valid")
} else {
fmt.Println("-----------------Not Valid---------")
}
}

}

OTP verified with negative time delta

I was able to validate a TOTP with negative delta, lets say the OTP was generated on x timestamp then validating OTP on x-10 timestamp is working.

The issue is not reproducible at go playground, there the validation in failing, here's the go playground link : https://go.dev/play/p/xmCJkmRkzXA

This is the go version I'm using at my machine

➜  temp git:(main) ✗ go version  
go version go1.21.3 darwin/amd64

library version

github.com/pquerna/otp v1.4.0

here are the logs I get at my machine

➜  temp git:(main) ✗ go run main.go
2023/11/09 07:39:10 otp generated 118067
2023/11/09 07:39:10 2023-11-09 07:39:10.457564 +0530 IST m=+0.000233439
2023/11/09 07:39:10 2023-11-09 07:39:00.457564 +0530 IST m=-9.999766561
2023/11/09 07:39:10 otp verified

logs from go-playground

2009/11/10 23:00:00 otp generated 470478
2009/11/10 23:00:00 2009-11-10 23:00:00 +0000 UTC m=+0.000000001
2009/11/10 23:00:00 2009-11-10 22:59:59 +0000 UTC m=-0.999999999
2009/11/10 23:00:00 otp verification failed

Program exited.

Replace strings.TrimRight with base32.NoPadding

In totp.Generate() there is
v.Set("secret", strings.TrimRight(base32.StdEncoding.EncodeToString(secret), "="))
to remove the "=" used for padding. Instead of trimming the result, the following should be used:
base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(secret))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.