Browse Source

Initial import

v1
maze 4 years ago
parent
commit
96bce646e1
2 changed files with 110 additions and 0 deletions
  1. +67
    -0
      cmd/totp/main.go
  2. +43
    -0
      otp.go

+ 67
- 0
cmd/totp/main.go View File

@ -0,0 +1,67 @@
package main
import (
"bytes"
"encoding/base32"
"encoding/base64"
"encoding/hex"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
otp "maze.io/otp.v1"
)
func main() {
period := flag.Int("period", 30, "period")
digits := flag.Int("digits", 6, "number of output digits")
encoding := flag.String("encoding", "base32", "secret encoding")
flag.Parse()
var (
secret, b []byte
err error
)
if flag.NArg() > 0 {
secret = []byte(flag.Arg(0))
} else {
if b, err = ioutil.ReadAll(os.Stdin); err != nil {
log.Fatalln(err)
}
secret = bytes.TrimSpace(b)
}
if len(secret) == 0 {
log.Fatalln("supply a secret either as argument or via stdin")
}
switch *encoding {
case "base32":
b = make([]byte, base32.StdEncoding.DecodedLen(len(secret)))
if _, err = base32.StdEncoding.Decode(b, secret); err != nil {
log.Fatalln(err)
}
secret = bytes.TrimSpace(b)
case "base64":
b = make([]byte, base64.StdEncoding.DecodedLen(len(secret)))
if _, err = base64.StdEncoding.Decode(b, secret); err != nil {
log.Fatalln(err)
}
secret = bytes.TrimSpace(b)
case "hex":
b = make([]byte, hex.DecodedLen(len(secret)))
if _, err = hex.Decode(b, secret); err != nil {
log.Fatalln(err)
}
secret = bytes.TrimSpace(b)
case "ascii", "utf8", "utf-8":
default:
log.Fatalln("unsupported encoding", *encoding)
}
log.Printf("secret: %q", secret)
fmt.Println(otp.TOTP(secret, *period, *digits))
}

+ 43
- 0
otp.go View File

@ -0,0 +1,43 @@
// Package otp is a bare bones RFC4226 / RFC6238 implementation.
package otp
import (
"crypto/hmac"
"crypto/sha1"
"fmt"
"math"
"strconv"
"time"
)
// HOTP generates a HMAC-based One-Time Password
func HOTP(secret []byte, counter int64, size int) string {
hash := hmac.New(sha1.New, secret)
hash.Write(counterBytes(counter))
bins := truncate(hash.Sum(nil))
hotp := int64(bins) % int64(math.Pow10(size))
return fmt.Sprintf("%0"+strconv.Itoa(size)+"d", hotp)
}
// TOTP generates a Time-based One-Time Password
func TOTP(secret []byte, period, size int) string {
now := (time.Now().Unix() / int64(period))
return HOTP(secret, now, size)
}
func counterBytes(counter int64) []byte {
b := make([]byte, 8)
for i := 7; i >= 0; i-- {
b[i] = byte(counter & 0xff)
counter = counter >> 8
}
return b
}
func truncate(hash []byte) int {
offset := int(hash[len(hash)-1] & 0xf)
return ((int(hash[offset]) & 0x7f) << 24) |
((int(hash[offset+1] & 0xff)) << 16) |
((int(hash[offset+2] & 0xff)) << 8) |
(int(hash[offset+3]) & 0xff)
}

Loading…
Cancel
Save