¿Existe una API pública para usar Google Authenticator ( autenticación de dos factores) en aplicaciones web de ejecución automática (por ejemplo, LAMP stack)?
¿Existe una API pública para usar Google Authenticator ( autenticación de dos factores) en aplicaciones web de ejecución automática (por ejemplo, LAMP stack)?
Respuestas:
El proyecto es de código abierto. No lo he usado. Pero está utilizando un algoritmo documentado (indicado en el RFC que figura en la página del proyecto de código abierto), y las implementaciones del autenticador admiten varias cuentas.
El proceso real es sencillo. El código de un tiempo es, esencialmente, un generador de números pseudoaleatorios. Un generador de números aleatorios es una fórmula que, una vez dada una semilla, o número inicial, continúa creando una secuencia de números aleatorios. Dada una semilla, mientras que los números pueden ser aleatorios entre sí, la secuencia en sí es determinista. Entonces, una vez que tenga su dispositivo y el servidor "sincronizados", los números aleatorios que crea el dispositivo, cada vez que presione el "botón de siguiente número", serán los mismos números aleatorios que el servidor espera.
Un sistema seguro de contraseña única es más sofisticado que un generador de números aleatorios, pero el concepto es similar. También hay otros detalles para ayudar a mantener el dispositivo y el servidor sincronizados.
Por lo tanto, no es necesario que otra persona aloje la autenticación, como, por ejemplo, OAuth. En su lugar, debe implementar ese algoritmo que sea compatible con las aplicaciones que Google proporciona para los dispositivos móviles. Ese software está (debería estar) disponible en el proyecto de código abierto.
Dependiendo de su sofisticación, debe tener todo lo que necesita para implementar el lado del servidor de este proceso para proporcionar el proyecto OSS y el RFC. No sé si hay una implementación específica para el software de su servidor (PHP, Java, .NET, etc.)
Pero, específicamente, no necesita un servicio externo para manejar esto.
El algoritmo está documentado en RFC6238 . Va un poco así:
He tenido un juego implementando el algoritmo en javascript aquí: http://blog.tinisles.com/2011/10/google-authenticator-one-time-password-algorithm-in-javascript/
Hay una variedad de bibliotecas para PHP (The LAMP Stack)
PHP
https://code.google.com/p/ga4php/
http://www.idontplaydarts.com/2011/07/google-totp-two-factor-authentication-for-php/
Debe tener cuidado al implementar la autenticación de dos factores, debe asegurarse de que sus relojes en el servidor y el cliente estén sincronizados, que haya protección contra ataques de fuerza bruta en el token y que la semilla inicial utilizada sea adecuadamente grande.
Puede usar mi solución , publicada como respuesta a mi pregunta (hay un código completo de Python y una explicación ):
Creo que es bastante fácil implementarlo en PHP o Perl. Si tiene algún problema con esto, hágamelo saber.
También publiqué mi código en GitHub como módulo de Python.
Encontré esto: https://github.com/PHPGangsta/GoogleAuthenticator . Lo probé y funciona bien para mí.
Hay: https://www.gauthify.com que lo ofrece como servicio
No es LAMP, pero si usa C #, este es el código que uso:
Código originario de:
https://github.com/kspearrin/Otp.NET
La clase Base32Encoding es de esta respuesta:
https://stackoverflow.com/a/7135008/3850405
Programa de ejemplo:
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");
}
}
Para aquellos que usan Laravel, este https://github.com/sitepoint-editors/google-laravel-2FA es una buena manera de resolver este problema.
Para usuarios de C #, ejecute esta aplicación de consola simple para comprender cómo verificar el código de token único. Tenga en cuenta que primero necesitamos instalar la biblioteca Otp.Net del paquete Nuget.
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!");
}
}
}