Google身份validation器可用作公共服务?

在自行运行(例如LAMP堆栈)Web应用程序中是否有使用Google Authenticator (双因素authentication)的公共API?

该项目是开源的。 我没有用过 但是它使用了文档化的algorithm(在开源项目页面列出的RFC中注明),并且validation器实现支持多个帐户。

实际过程很简单。 一次性代码本质上是一个伪随机数发生器。 一个随机数发生器是一个公式,一旦给出一个种子或起始数字,继续创build一个随机数字stream。 给定一个种子,而数字可能是相互随机的,序列本身是确定性的。 所以,一旦你有你的设备和服务器“同步”,那么设备创build的随机数,每次你点击“下一个数字button”,将是相同的,随机的,数字服务器预期。

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

所以,不需要其他人来主持authentication,比如OAuth。 相反,您需要实施与Google为移动设备提供的应用兼容的algorithm。 该软件是(应该)可用在开源项目上。

根据你的复杂程度,你应该拥有所有你需要实现的服务器端的这个过程,给出OSS项目和RFC。 我不知道你的服务器软件是否有特定的实现(PHP,Java,.NET等)

但是,具体而言,您不需要现场服务来处理这个问题。

该algorithmlogging在RFC6238中 。 有点像这样:

  • 您的服务器为用户提供了一个安装到Google Authenticator的秘密。 谷歌做这个QR代码在这里logging 。
  • Google Authenticator通过Unix时间的SHA1-HMAC和秘密生成一个6位代码(在RFC中有更多的细节)
  • 服务器也知道secret / unix时间来validation6位数的代码。

我已经在javascript中实现了这个algorithm: http : //blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/

PHP有多种库(LAMP堆栈)

PHP

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

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

在执行双因素validation时,您应该小心,您需要确保您的服务器和客户端上的时钟同步,确保对令牌的暴力攻击采取了保护措施,并且使用的初始种子适当大。

你可以使用我的解决scheme ,张贴作为我的问题的答案(有完整的Python代码解释 ):

Google身份validation器在Python中的实现

我想,在PHP或Perl中实现它是相当容易的。 如果您有任何问题,请告诉我。

我也把我的代码作为Python模块发布在GitHub上 。

我发现这个: https : //github.com/PHPGangsta/GoogleAuthenticator 。 我testing了它,并为我工作得很好。

Theres: https ://www.gauthify.com提供它作为服务

是的,不需要networking服务,因为谷歌身份validation器应用程序不会与谷歌服务器通信,它只是保持与您的服务器生成的初始秘密同步(input到您的手机从QR码),而时间的stream逝。

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

代码最初来自:

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

Base32Encoding类是从这个答案:

https://stackoverflow.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(); } } 

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