Files
codit/backend/internal/auth/totp.go
2026-06-01 23:18:46 +09:00

91 lines
2.2 KiB
Go

package auth
import "crypto/hmac"
import "crypto/rand"
import "crypto/sha1"
import "encoding/base32"
import "encoding/binary"
import "fmt"
import "net/url"
import "strconv"
import "strings"
import "time"
const TOTPPeriod int64 = 30
const TOTPDigits int = 6
func NewTOTPSecret() (string, error) {
var raw []byte
var err error
var enc *base32.Encoding
raw = make([]byte, 20)
_, err = rand.Read(raw)
if err != nil {
return "", err
}
enc = base32.StdEncoding.WithPadding(base32.NoPadding)
return enc.EncodeToString(raw), nil
}
func TOTPProvisioningURL(issuer string, account string, secret string) string {
var label string
var values url.Values
label = strings.TrimSpace(issuer) + ":" + strings.TrimSpace(account)
values = url.Values{}
values.Set("secret", strings.TrimSpace(secret))
values.Set("issuer", strings.TrimSpace(issuer))
values.Set("algorithm", "SHA1")
values.Set("digits", strconv.Itoa(TOTPDigits))
values.Set("period", strconv.FormatInt(TOTPPeriod, 10))
return "otpauth://totp/" + url.PathEscape(label) + "?" + values.Encode()
}
func ValidateTOTP(code string, secret string, now time.Time) bool {
var value string
var counter int64
var i int64
value = strings.TrimSpace(code)
if len(value) != TOTPDigits {
return false
}
counter = now.Unix() / TOTPPeriod
for i = -1; i <= 1; i++ {
if totpCode(secret, counter+i) == value {
return true
}
}
return false
}
func totpCode(secret string, counter int64) string {
var key []byte
var msg []byte
var mac hashHMAC
var sum []byte
var offset byte
var binaryCode uint32
var code uint32
var enc *base32.Encoding
var err error
enc = base32.StdEncoding.WithPadding(base32.NoPadding)
key, err = enc.DecodeString(strings.ToUpper(strings.TrimSpace(secret)))
if err != nil {
return ""
}
msg = make([]byte, 8)
binary.BigEndian.PutUint64(msg, uint64(counter))
mac = hmac.New(sha1.New, key)
_, _ = mac.Write(msg)
sum = mac.Sum(nil)
offset = sum[len(sum)-1] & 0x0f
binaryCode = (uint32(sum[offset])&0x7f)<<24 | (uint32(sum[offset+1])&0xff)<<16 | (uint32(sum[offset+2])&0xff)<<8 | (uint32(sum[offset+3]) & 0xff)
code = binaryCode % 1000000
return fmt.Sprintf("%06d", code)
}
type hashHMAC interface {
Write([]byte) (int, error)
Sum([]byte) []byte
}