Google Authenticator available as a public service?

前端 未结 10 1277
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-04 04:24

Is there public API for using the Google Authenticator (two factor authentication) on self-running (e.g. LAMP stack) web apps?

相关标签:
10条回答
  • 2020-12-04 05:22

    Not LAMP but if you use C# this is the code I use:

    Code originally from:

    https://github.com/kspearrin/Otp.NET

    The Base32Encoding class is from this answer:

    https://stackoverflow.com/a/7135008/3850405

    Example program:

    class Program
    {
        static void Main(string[] args)
        {
            var bytes = Base32Encoding.ToBytes("JBSWY3DPEHPK3PXP");
    
            var totp = new Totp(bytes);
    
            var result = totp.ComputeTotp();
            var remainingTime = totp.RemainingSeconds();
        }
    }
    

    Totp:

    public class Totp
    {
        const long unixEpochTicks = 621355968000000000L;
    
        const long ticksToSeconds = 10000000L;
    
        private const int step = 30;
    
        private const int totpSize = 6;
    
        private byte[] key;
    
        public Totp(byte[] secretKey)
        {
            key = secretKey;
        }
    
        public string ComputeTotp()
        {
            var window = CalculateTimeStepFromTimestamp(DateTime.UtcNow);
    
            var data = GetBigEndianBytes(window);
    
            var hmac = new HMACSHA1();
            hmac.Key = key;
            var hmacComputedHash = hmac.ComputeHash(data);
    
            int offset = hmacComputedHash[hmacComputedHash.Length - 1] & 0x0F;
            var otp = (hmacComputedHash[offset] & 0x7f) << 24
                   | (hmacComputedHash[offset + 1] & 0xff) << 16
                   | (hmacComputedHash[offset + 2] & 0xff) << 8
                   | (hmacComputedHash[offset + 3] & 0xff) % 1000000;
    
            var result = Digits(otp, totpSize);
    
            return result;
        }
    
        public int RemainingSeconds()
        {
            return step - (int)(((DateTime.UtcNow.Ticks - unixEpochTicks) / ticksToSeconds) % step);
        }
    
        private byte[] GetBigEndianBytes(long input)
        {
            // Since .net uses little endian numbers, we need to reverse the byte order to get big endian.
            var data = BitConverter.GetBytes(input);
            Array.Reverse(data);
            return data;
        }
    
        private long CalculateTimeStepFromTimestamp(DateTime timestamp)
        {
            var unixTimestamp = (timestamp.Ticks - unixEpochTicks) / ticksToSeconds;
            var window = unixTimestamp / (long)step;
            return window;
        }
    
        private string Digits(long input, int digitCount)
        {
            var truncatedValue = ((int)input % (int)Math.Pow(10, digitCount));
            return truncatedValue.ToString().PadLeft(digitCount, '0');
        }
    
    }
    

    Base32Encoding:

    public static class Base32Encoding
    {
        public static byte[] ToBytes(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                throw new ArgumentNullException("input");
            }
    
            input = input.TrimEnd('='); //remove padding characters
            int byteCount = input.Length * 5 / 8; //this must be TRUNCATED
            byte[] returnArray = new byte[byteCount];
    
            byte curByte = 0, bitsRemaining = 8;
            int mask = 0, arrayIndex = 0;
    
            foreach (char c in input)
            {
                int cValue = CharToValue(c);
    
                if (bitsRemaining > 5)
                {
                    mask = cValue << (bitsRemaining - 5);
                    curByte = (byte)(curByte | mask);
                    bitsRemaining -= 5;
                }
                else
                {
                    mask = cValue >> (5 - bitsRemaining);
                    curByte = (byte)(curByte | mask);
                    returnArray[arrayIndex++] = curByte;
                    curByte = (byte)(cValue << (3 + bitsRemaining));
                    bitsRemaining += 3;
                }
            }
    
            //if we didn't end with a full byte
            if (arrayIndex != byteCount)
            {
                returnArray[arrayIndex] = curByte;
            }
    
            return returnArray;
        }
    
        public static string ToString(byte[] input)
        {
            if (input == null || input.Length == 0)
            {
                throw new ArgumentNullException("input");
            }
    
            int charCount = (int)Math.Ceiling(input.Length / 5d) * 8;
            char[] returnArray = new char[charCount];
    
            byte nextChar = 0, bitsRemaining = 5;
            int arrayIndex = 0;
    
            foreach (byte b in input)
            {
                nextChar = (byte)(nextChar | (b >> (8 - bitsRemaining)));
                returnArray[arrayIndex++] = ValueToChar(nextChar);
    
                if (bitsRemaining < 4)
                {
                    nextChar = (byte)((b >> (3 - bitsRemaining)) & 31);
                    returnArray[arrayIndex++] = ValueToChar(nextChar);
                    bitsRemaining += 5;
                }
    
                bitsRemaining -= 3;
                nextChar = (byte)((b << bitsRemaining) & 31);
            }
    
            //if we didn't end with a full char
            if (arrayIndex != charCount)
            {
                returnArray[arrayIndex++] = ValueToChar(nextChar);
                while (arrayIndex != charCount) returnArray[arrayIndex++] = '='; //padding
            }
    
            return new string(returnArray);
        }
    
        private static int CharToValue(char c)
        {
            int value = (int)c;
    
            //65-90 == uppercase letters
            if (value < 91 && value > 64)
            {
                return value - 65;
            }
            //50-55 == numbers 2-7
            if (value < 56 && value > 49)
            {
                return value - 24;
            }
            //97-122 == lowercase letters
            if (value < 123 && value > 96)
            {
                return value - 97;
            }
    
            throw new ArgumentException("Character is not a Base32 character.", "c");
        }
    
        private static char ValueToChar(byte b)
        {
            if (b < 26)
            {
                return (char)(b + 65);
            }
    
            if (b < 32)
            {
                return (char)(b + 24);
            }
    
            throw new ArgumentException("Byte is not a value Base32 value.", "b");
        }
    
    }
    
    0 讨论(0)
  • 2020-12-04 05:24

    You can use my solution, posted as the answer to my question (there is full Python code and explanation):

    Google Authenticator implementation in Python

    It is rather easy to implement it in PHP or Perl, I think. If you have any problems with this, please let me know.

    I have also posted my code on GitHub as Python module.

    0 讨论(0)
  • 2020-12-04 05:24

    I found this: https://github.com/PHPGangsta/GoogleAuthenticator. I tested it and works fine for me.

    0 讨论(0)
  • 2020-12-04 05:26

    The project is open source. I have not used it. But it's using a documented algorithm (noted in the RFC listed on the open source project page), and the authenticator implementations support multiple accounts.

    The actual process is straightforward. The one time code is, essentially, a pseudo random number generator. A random number generator is a formula that once given a seed, or starting number, continues to create a stream of random numbers. Given a seed, while the numbers may be random to each other, the sequence itself is deterministic. So, once you have your device and the server "in sync" then the random numbers that the device creates, each time you hit the "next number button", will be the same, random, numbers the server expects.

    A secure one time password system is more sophisticated than a random number generator, but the concept is similar. There are also other details to help keep the device and server in sync.

    So, there's no need for someone else to host the authentication, like, say OAuth. Instead you need to implement that algorithm that is compatible with the apps that Google provides for the mobile devices. That software is (should be) available on the open source project.

    Depending on your sophistication, you should have all you need to implement the server side of this process give the OSS project and the RFC. I do not know if there is a specific implementation for your server software (PHP, Java, .NET, etc.)

    But, specifically, you don't need an offsite service to handle this.

    0 讨论(0)
提交回复
热议问题