Google 身份验证器可作为公共服务使用吗?

Posted

技术标签:

【中文标题】Google 身份验证器可作为公共服务使用吗?【英文标题】:Google Authenticator available as a public service? 【发布时间】:2011-07-02 12:02:13 【问题描述】:

是否有用于在自运行(例如 LAMP 堆栈)网络应用程序上使用 Google Authenticator(两因素身份验证)的公共 API?

【问题讨论】:

【参考方案1】:

project 是开源的。我没用过。但它使用的是文档化算法(在开源项目页面上列出的 RFC 中注明),并且身份验证器实现支持多个帐户。

实际过程很简单。一次性代码本质上是一种伪随机数生成器。随机数生成器是一个公式,一旦给定种子或起始数,就会继续创建随机数流。给定一个种子,虽然数字可能彼此随机,但序列本身是确定性的。因此,一旦您的设备和服务器“同步”,那么每次您点击“下一个数字按钮”时,设备创建的随机数将与服务器期望的随机数相同。

安全的一次性密码系统比随机数生成器更复杂,但概念相似。还有其他细节有助于保持设备和服务器同步。

因此,不需要其他人来主持身份验证,例如 OAuth。相反,您需要实现与 Google 为移动设备提供的应用程序兼容的算法。该软件(应该)在开源项目中可用。

根据您的复杂程度,您应该拥有实现此过程的服务器端所需的一切,并提供 OSS 项目和 RFC。不知道你们的服务器软件有没有具体的实现(php、Java、.NET等)

但是,具体来说,您不需要异地服务来处理这个问题。

【讨论】:

另一方面,在许多不同的移动设备上使用现有的、众所周知的、易于获得的解决方案是非常有益的......(提示提示) 你是说短信?它速度慢、不可靠且成本高。 我写了一篇关于如何在纯 java 中为网站实现 Google Authenticator/RFC6238 兼容的 2fa:asaph.org/2016/04/google-authenticator-2fa-java.html(无耻插件) 自 2016 年 8 月起,仅供参考 NIST 不再推荐使用 SMS 进行双重身份验证。别管它认为不安全的成本。【参考方案2】:

有多种 PHP 库(LAMP 堆栈)

PHP

https://code.google.com/p/ga4php/

http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/

在实施两因素身份验证时应小心,您需要确保服务器和客户端上的时钟同步,有适当的保护措施防止对令牌的暴力攻击,并且使用的初始种子是适当的大。

【讨论】:

内容很棒,但是任何使用第一个链接的人都应该实施SQL注入预防方法,因为存在一些潜在的缺陷。看看第一个提出的问题。第二个链接是完美的。【参考方案3】:

该算法记录在RFC6238 中。有点像这样:

您的服务器为用户提供了安装到 Google Authenticator 中的密码。谷歌将其作为二维码记录在here。 Google Authenticator 通过 Unix 时间的 SHA1-HMAC 和密钥生成一个 6 位代码(RFC 中有更多详细信息) 服务器也知道密钥/unix 时间来验证 6 位代码。

我在这里用 javascript 实现了算法:http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

【讨论】:

【参考方案4】:

您可以使用我的解决方案,作为我问题的答案发布(有完整的 Python 代码解释):

Google Authenticator implementation in Python

我认为用 PHP 或 Perl 实现它相当容易。如果您对此有任何问题,请告诉我。

我还有 posted my code on GitHub 作为 Python 模块。

【讨论】:

事后... 【参考方案5】:

有:https://www.gauthify.com 将其作为服务提供

【讨论】:

【参考方案6】:

我发现了这个:https://github.com/PHPGangsta/GoogleAuthenticator。我对其进行了测试,对我来说效果很好。

【讨论】:

我想你会更喜欢github.com/RobThree/TwoFactorAuth。它基于上述库,但对其进行了巨大改进,具有更多功能和更清晰的文档。【参考方案7】:

是的,不需要网络服务,因为 Google Authenticator 应用程序不会与 google 服务器通信,它只是与您的服务器生成的初始密码(从 QR 码输入您的手机)在时间流逝时保持同步。

【讨论】:

【参考方案8】:

对于那些使用 Laravel 的人来说,https://github.com/sitepoint-editors/google-laravel-2FA 是解决这个问题的好方法。

【讨论】:

【参考方案9】:

不是 LAMP,但如果您使用 C#,这是我使用的代码:

代码来自:

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

Base32Encoding 类来自这个答案:

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

示例程序:

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();
    

托普:

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');
    


Base32编码:

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");
    


【讨论】:

【参考方案10】:

对于 C# 用户,运行这个简单的控制台应用程序以了解如何验证一次性令牌代码。请注意,我们需要先从 Nuget 包中安装库 Otp.Net。

static string secretKey = "JBSWY3DPEHPK3PXP"; //add this key to your Google Authenticator app  

private static void Main(string[] args)

        var bytes = Base32Encoding.ToBytes(secretKey);

        var totp = new Totp(bytes);

        while (true)
        
            Console.Write("Enter your code from Google Authenticator app: ");
            string userCode = Console.ReadLine();

            //Generate one time token code
            string tokenInApp = totp.ComputeTotp();
            int remainingSeconds = totp.RemainingSeconds();

            if (userCode.Equals(tokenInApp)
                && remainingSeconds > 0)
            
                Console.WriteLine("Success!");
            
            else
            
                Console.WriteLine("Failed. Try again!");
            
        

【讨论】:

以上是关于Google 身份验证器可作为公共服务使用吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何为 Google Cloud Scheduler 设置基本身份验证

SonarQube 在具有集成身份验证的 SQL Server 上作为 Windows 服务运行 - 有人有工作示例吗?

作为银行账户持有人,我可以使我之前提供的所有格子身份验证无效吗?

Google Cloud Tasks 无法向 Cloud Run 进行身份验证

Google+ 身份验证:refreshToken() 返回 invalid_grant 错误

使用 php 进行 jwt 服务器端身份验证