Google Authenticator es algo que muchos de nosotros usamos todo el tiempo, pero ¿cuántos de nosotros realmente entendemos cómo funciona bajo el capó?
En este tutorial, desmitificaré la magia detrás de los códigos OTP que caducan de Google Authenticator y le mostraré cómo agregar soporte de Google Authenticator a su aplicación desde cero, en JavaScript.
Como parte del programa acelerador de carreras de Pesto , tuve la oportunidad de contribuir a este increíble proyecto de código abierto llamado Enquirer . Le permite crear mensajes de línea de comandos con estilo que son fáciles de usar y de crear. Al momento de escribir este artículo, Enquirer tiene más de 4500 estrellas Github y es utilizado por más de 7500 repositorios.
Una de las funciones que agregué a Enquirer fue la capacidad de verificar contraseñas de un solo uso basadas en el tiempo, directamente desde la línea de comandos, generadas por cualquier aplicación de autenticación de dos factores, como Google Authenticator , sin necesidad de una conexión a Internet.
La OTP basada en el tiempo (TOTP) es un algoritmo que tiene en cuenta el tiempo actual para generar una contraseña única de un solo uso.
En la era actual, es obvio que las contraseñas por sí solas no pueden mantener alejados a los malos. Necesita una capa adicional de seguridad, un segundo factor.
Hay muchos tipos de sistemas de autenticación de dos factores, pero para que funcionen, deben ser seguros y fáciles de usar. TOTP marca ambas casillas perfectamente.
Es seguro porque:
Es fácil de usar porque:
Usé Google Authenticator como aplicación móvil para verificar contraseñas de un solo uso.
Google Authenticator genera OTP basadas en el tiempo que se calculan utilizando el algoritmo especificado en RFC6238 . La aplicación también es compatible con las OTP basadas en HMAC calculadas con el algoritmo especificado en RFC4226 .
Las OTP basadas en el tiempo se basan en el algoritmo de las OTP basadas en HMAC (HOTP). En este artículo, implementaremos ambos algoritmos usando NodeJS (JavaScript).
En primer lugar, tendremos que crear una clave secreta específica de la aplicación que se utilizará para verificar las OTP. Esto también se compartirá con la aplicación Google Authenticator.
La clave secreta es cómo las aplicaciones de autenticación saben qué OTP generar para una aplicación/sitio web específico. El secreto se puede generar de muchas formas y esta es una de las implementaciones.
Generaremos un búfer de datos completamente aleatorio usando el incorporado
crypto
biblioteca NodeJS y luego codificaremos los datos en base32 usando hi-base32
módulo npm . const crypto = require ( 'crypto' ); const base32 = require ( 'hi-base32' ); function generateSecret ( length = 20 ) { const randomBuffer = crypto.randomBytes(length); return base32.encode(randomBuffer).replace( /=/g , '' ); }
Esta función devolverá una cadena aleatoria que es nuestra clave secreta.
Ingrese esto en la aplicación Google Authenticator. Verificaremos las OTP para este secreto, utilizando nuestra implementación.
Para generar HOTP necesitamos una clave secreta y un valor de contador. Ya tenemos un secreto. No necesitamos preocuparnos por el contador a partir de ahora porque proporcionaremos su valor cuando generemos TOTP.
Lo esencial
Digamos que nuestra función que genera HOTP acepta dos argumentos,
secret
y counter
. Primero, decodificaremos el secreto. const decodedSecret = base32.decode.asBytes(secret);
Ahora vamos a crear un búfer a partir de la
counter
valor. const buffer = Buffer.alloc( 8 ); for ( let i = 0 ; i < 8 ; i++) { buffer[ 7 - i] = counter & 0xff ; counter = counter >> 8 ; }
Según RFC4226 , tenemos tres pasos principales para generar un HOTP.
Paso 1: generar un valor HMAC
Google Authenticator utiliza el algoritmo SHA1 para crear HMAC. Usaremos funciones de la
crypto
biblioteca para crear un HMAC (usando SHA1), actualice el creado anteriormente buffer
con esto y luego producir un valor HMAC; const hmac = crypto.createHmac( 'sha1' , Buffer.from(decodedSecret)); hmac.update(buffer); const hmacResult = hmac.digest();
hmacResult
es una cadena de 20 bytes que es un valor HMAC-SHA-1. Paso 2: truncamiento dinámico
El propósito del truncamiento dinámico es extraer un código binario dinámico de 4 bytes de un resultado HMAC-SHA-1 de 20 bytes. Estos pasos se toman directamente de RFC4226 .
const offset = hmacValue[hmacValue.length - 1 ] & 0xf ; const code = ((hmacValue[offset] & 0x7f ) << 24 ) | ((hmacValue[offset + 1 ] & 0xff ) << 16 ) | ((hmacValue[offset + 2 ] & 0xff ) << 8 ) | (hmacValue[offset + 3 ] & 0xff );
Paso 3: Calcule el valor HOTP
Google Authenticator genera contraseñas de seis dígitos, por lo que extraeremos los primeros seis dígitos de la
code
para obtener nuestro valor HOTP final. const hotp = code % ( 10 ** 6 );
Poniéndolo todo junto, la función generateHOTP se puede escribir como:
const crypto = require ( 'crypto' ); const base32 = require ( 'hi-base32' ); function generateHOTP ( secret, counter ) { const decodedSecret = base32.decode.asBytes(secret); const buffer = Buffer.alloc( 8 ); for ( let i = 0 ; i < 8 ; i++) { buffer[ 7 - i] = counter & 0xff ; counter = counter >> 8 ; } // Step 1: Generate an HMAC-SHA-1 value const hmac = crypto.createHmac( 'sha1' , Buffer.from(decodedSecret)); hmac.update(buffer); const hmacResult = hmac.digest(); // Step 2: Generate a 4-byte string (Dynamic Truncation) const code = dynamicTruncationFn(hmacResult); // Step 3: Compute an HOTP value return code % 10 ** 6 ; } function dynamicTruncationFn ( hmacValue ) { const offset = hmacValue[hmacValue.length - 1 ] & 0xf ; return ( ((hmacValue[offset] & 0x7f ) << 24 ) | ((hmacValue[offset + 1 ] & 0xff ) << 16 ) | ((hmacValue[offset + 2 ] & 0xff ) << 8 ) | (hmacValue[offset + 3 ] & 0xff ) ); }
Tenemos una función con la que podemos generar contraseñas de un solo uso basadas en HMAC. Ahora podemos usar esta función para generar contraseñas de un solo uso basadas en el tiempo.
El algoritmo para las OTP basadas en el tiempo simplemente usa el tiempo actual de Unix (con un paso de tiempo de 30 segundos) como el
counter
valor en el generateHOTP
función.Podemos obtener la hora actual en JavaScript usando
Date.now()
. Pero esto devuelve el número de milisegundos transcurridos desde January 1, 1970, 00:00:00 UTC
. Tenemos que convertir el paso de tiempo de 1 milisegundo a paso de tiempo de 30 segundos. const counter = Math .floor( Date .now() / 30000 );
También aceptamos un
window
argumento que nos ayuda a obtener el TOTP de cualquier ventana de tiempo desde la hora actual. Entonces, si queremos saber qué TOTP se generó antes de 2 minutos a partir de ahora, configuramos window = -4
. Esto calcula el TOTP en 4 pasos de tiempo (de 30 segundos cada uno) antes de la hora actual.Entonces nuestro
generateTOTP
función se puede escribir como: function generateTOTP ( secret, window = 0 ) { const counter = Math .floor( Date .now() / 30000 ); return generateHOTP(secret, counter + window ); }
Para verificar los TOTP generados en la aplicación Google Authenticator, necesitamos la clave secreta. Una vez que tenemos la clave secreta podemos usar el
generateTOTP
función anterior y calcule el TOTP para ver si coincide o no.A veces, es posible que, mientras un usuario escribe la OTP, pase la ventana actual de 30 segundos y la OTP que ingresó falle. Para mejorar la experiencia del usuario, no solo verificamos el TOTP generado en la ventana de paso de tiempo actual, sino también un paso antes y un paso después de la ventana actual.
If the current time is T⁰, we'll verify the TOTP for these steps: T⁰ – 1 T⁰ T⁰ + 1
También podemos usar una ventana más amplia, pero hacerlo puede ser un riesgo para la seguridad. Si usamos una ventana de
T⁰ ± 20
, por ejemplo, ¡esto significaría que una OTP generada hace 10 minutos todavía es válida!Teniendo esto en cuenta, nuestro
verifyTOTP
función se puede escribir como: function verifyTOTP ( token, secret, window = 1 ) { if ( Math .abs(+ window ) > 10 ) { console .error( 'Window size is too large' ); return false ; } for ( let errorWindow = - window ; errorWindow <= + window ; errorWindow++) { const totp = generateTOTP(secret, errorWindow); if (token === totp) { return true ; } } return false ; }
token
es el TOTP de Google Authenticator. estamos invocando generateTOTP
función para calcular los TOTP para todas las ventanas y verificar si coincide con el token ingresado.Esta función devuelve
true
si el token se verifica con éxito. Esto completa la implementación de la autenticación de dos factores (TOTP) con Google Authenticator.He eliminado algunas comprobaciones de errores para que este artículo sea sencillo. Puede encontrar el código completo en esta solicitud de extracción.
Asegúrate de consultar la biblioteca de Enquirer y colocar una estrella de Github. ¡Gracias por leer!
Hola, mi nombre es Rajat. Soy un verdadero ingeniero de software full-stack con experiencia trabajando en todo, desde parchear controladores de U-boot y Linux en C hasta lanzar productos mejor calificados en Product Hunt.