Task: Time-based one-time password algorithm



C#: using System;
using System.Security.Cryptography;

namespace RosettaTOTP
{
    public class TOTP_SHA1
    {
        private byte[] K;
        public TOTP_SHA1()
        {
            GenerateKey();
        }
        public void GenerateKey()
        {
            using (RandomNumberGenerator rng = new RNGCryptoServiceProvider())
            {
                
                K = new byte[HMACSHA1.Create().HashSize / 8];
                rng.GetBytes(K);
            }
        }
        public int HOTP(UInt64 C, int digits = 6)
        {
            var hmac = HMACSHA1.Create();
            hmac.Key = K;
            hmac.ComputeHash(BitConverter.GetBytes(C));
            return Truncate(hmac.Hash, digits);
        }
        public UInt64 CounterNow(int T1 = 30)
        {
            var secondsSinceEpoch = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
            return (UInt64)Math.Floor(secondsSinceEpoch / T1);
        }
        private int DT(byte[] hmac_result)
        {
            int offset = hmac_result[19] & 0xf;
            int bin_code = (hmac_result[offset] & 0x7f) << 24
               | (hmac_result[offset + 1] & 0xff) << 16
               | (hmac_result[offset + 2] & 0xff) << 8
               | (hmac_result[offset + 3] & 0xff);
            return bin_code;
        }

        private int Truncate(byte[] hmac_result, int digits)
        {
            var Snum = DT(hmac_result);
            return Snum % (int)Math.Pow(10, digits);
        }
    }


    class Program
    {
        static void Main(string[] args)
        {
            var totp = new TOTP_SHA1();
            Console.WriteLine(totp.HOTP(totp.CounterNow()));
        }
    }
}


Go: 


package onetime

import (
	"crypto/hmac"
	"crypto/sha1"
	"encoding/binary"
	"errors"
	"hash"
	"math"
	"time"
)


type OneTimePassword struct {
	Digit    int              
	TimeStep time.Duration    
	BaseTime time.Time        
	Hash     func() hash.Hash 
}


func (otp *OneTimePassword) HOTP(secret []byte, count uint64) uint {
	hs := otp.hmacSum(secret, count)
	return otp.truncate(hs)
}

func (otp *OneTimePassword) hmacSum(secret []byte, count uint64) []byte {
	mac := hmac.New(otp.Hash, secret)
	binary.Write(mac, binary.BigEndian, count)
	return mac.Sum(nil)
}

func (otp *OneTimePassword) truncate(hs []byte) uint {
	sbits := dt(hs)
	snum := uint(sbits[3]) | uint(sbits[2])<<8
	snum |= uint(sbits[1])<<16 | uint(sbits[0])<<24
	return snum % uint(math.Pow(10, float64(otp.Digit)))
}




func Simple(digit int) (otp OneTimePassword, err error) {
	if digit < 6 {
		err = errors.New("minimum of 6 digits is required for a valid HTOP code")
		return
	} else if digit > 9 {
		err = errors.New("HTOP code cannot be longer than 9 digits")
		return
	}
	const step = 30 * time.Second
	otp = OneTimePassword{digit, step, time.Unix(0, 0), sha1.New}
	return
}


func (otp *OneTimePassword) TOTP(secret []byte) uint {
	return otp.HOTP(secret, otp.steps(time.Now()))
}

func (otp *OneTimePassword) steps(now time.Time) uint64 {
	elapsed := now.Unix() - otp.BaseTime.Unix()
	return uint64(float64(elapsed) / otp.TimeStep.Seconds())
}

func dt(hs []byte) []byte {
	offset := int(hs[len(hs)-1] & 0xf)
	p := hs[offset : offset+4]
	p[0] &= 0x7f
	return p
}