Funcionalidad
La implementación de la solución contempla dos flujos principales, el de enrolamiento y el de autorización, a través de los cuales interactúan los servicios que conforman la solución.
Enrolamiento/Activación
Durante el enrolamiento se vincula un dispositivo o cuenta para permitir la generación de token digital.
Este flujo consta de los siguientes pasos:
El usuario inicia sesión desde el dispositivo y / o realiza la solicitud de enrolamiento.
Los servicios de backend realizan la autenticación del usuario y de un segundo factor para garantizar la validación de identidad y del dispositivo a vincular
Una vez autenticado el usuario se realizan los procesos de inicio de sesión.
La respuesta se transmitida hacia el dispositivo.
El dispositivo recibe respuesta, y de no encontrarse error, genera un par de claves RSA codificadas mediante PKCS #8, y envía la solicitud de enrolamiento junto con la clave pública generada y el algoritmo utilizado para generar las claves (RSA).
El servicio de backend de Spin recibe la solicitud y la transmite al servicio de agregación.
El servicio de agregación solicita el enrolamiento de la cuenta al servicio de Token
El servicio de token invoca a HSM adapter para generar una clave nueva
HSM adapter genera una clave aleatoria utilizando java.security.MessageDigest utilizando la hora actual como salt.
Se cifra la clave aleatoria generada mediante AWS CloudHSM utilizando el algoritmo AES.
La clave cifrada por AWS CloudHSM es retornada a HSM adapter.
Se cifra la clave aleatoria generada en el paso 9 con la clave pública generada desde el dispositivo para ser retornada al dispositivo de forma segura (clientKey) junto con la clave cifrada mediante AWS CloudHSM (serverKey)
El servicio de Token almacena la clave cifrada mediante AWS CloudHSM en la base de datos, dejando como llave de registro el identificador de cuenta ofuscado.
El servicio de Token retorna la clave recibida por HSM adapter para enviar al dispositivo la reenvía al servicio de agregación con el nombre seed dentro del cuerpo de la respuesta.
El servicio de agregación reenvía la respuesta a los servicios de backend.
Los servicios de backend comunican la respuesta al dispositivo, que recupera la clave cifrada desde HSM adapter con la llave publica (seed) y la descifra con la llave privada generada anteriormente. Una vez descifrada la clave, se almacena de forma segura dentro del dispositivo.
Autenticación mediante token digital
Una vez se ha enrolado el dispositivo, el usuario puede hacer uso de los servicios de token digital para validar que el dispositivo desde el cual realiza las transacciones es el mismo desde el cual se enroló previamente.
Los pasos que sigue éste flujo son los siguientes:
El usuario solicita la ejecución de una acción protegida desde el dispositivo. Internamente el dispositivo lee la clave criptográfica almacenada durante el proceso de enrolamiento y genera un TOTP utilizando la hora actual. Posteriormente envía el TOTP junto con el cuerpo de la solicitud de validación.
Los servicios de backend reciben la petición y la redirigen al servicio de agregación.
El servicio de agregación retransmite la petición el servicio de Token
El servicio de token ofusca el identificador de cuenta de usuario y lo usa como clave de acceso de registro para leer de la base de datos.
El servicio de token recupera la clave cifrada de la base de datos
El servicio de Token envía el TOTP generado por el cliente junto con la clave cifrada a HSM adapter.
HSM adapter envía la clave cifrada a AWS ClodHSM para su descifrado.
AWS CloudHSM descifra la clave y la retorna a HSM Adapter.
HSM adapter genera un nuevo TOTP con la clave cifrada.
Se compara el TOTP generado con el TOTP generado por el dispositivo. Si se encuentran en la misma ventana de tiempo el valor debe ser igual y la autenticación es exitosa.
El resultado es retornado al servicio de Token
El servicio de Token retransmite el resultado al servicio de agregación
El servicio de agregación reenvía respuesta a servicios de backend, los cuales, en el caso de Spin, de ser exitosa generan un nuevo token para ser añadido en la cabecera que la transacción a autorizar desde el dispositivo.
El dispositivo recibe la respuesta de la autenticación y en el caso de Spin, añaden e token a el encabezado de la solicitud de transacción.
Implementación del algoritmos
Generación de claves asimétricas desde el dispositivo
Con el fin de garantizar que solamente el dispositivo y el servicio de generación de TOTP conozcan la clave criptográfica, desde el dispositivo se debe generar un par de claves asimétricas. La implementación que se realizó en javascript para probar la solución fue la siguiente:
import * as NodeRSA from 'node-rsa'
function generateRSAKeys () {
const key = new NodeRSA({ b: 2048 })
return {
publicKey: key.exportKey('pkcs8-public-der'),
privateKey: key.exportKey('pkcs8-private-der')
}
}
export function decrypt (message, privateKey) {
var key = NodeRSA(privateKey, 'pkcs8-private-pem', {'environment': 'node', 'encryptionScheme': 'pkcs1', 'signingScheme': 'sha256'})
return key.decrypt(message, 'utf8')
}
export function encrypt (message, publicKey) {
const key = new NodeRSA()
key.importKey(publicKey, 'pkcs8-public')
return key.encrypt(message, 'base64')
}La anterior implementación genera la clave pública y privada en formato PKCS #8; las claves serán utilizadas en Java con un código similar la siguiente:
final var X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicKeyBytes);
final var keyFactory = KeyFactory.getInstance(algorithm);
final var publicKey = keyFactory.generatePublic(keySpec);
var cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.ENCRYPT_MODE,publicKey);
final var clientKey = cipher.doFinal(keyBytes);
Como se mencionó anteriormente la validación de la funcionalidad se hizo usando RSA. Es posible utilizar otros algoritmos, sin embargo deben satisfacer la implementación anterior, y en algunos casos puede ser necesario especificar la codificación, lo que implicaría añadir un nuevo parámetro al servicio HSM adapter.
Generación de TOTP
Para generar el TOTP desde el dispositivo se desarrolló la siguiente implementación:
export function getTotp (seed) {
const digits = 9
// Get the current time in 30-second intervals
const time = Math.floor(Date.now() / 1000 / 30)
// Convert the time in hex String
let timeInHex = BigInt(time).toString(16).toUpperCase().padEnd(16, '0')
var msg = Buffer.from(timeInHex, 'base64')
const key = Buffer.from(seed, 'base64')
const hmac = crypto.createHmac('sha256', key)
hmac.update(msg)
const hash = hmac.digest()
var offset = hash[hash.length - 1] & 0xf
var binary = ((hash[offset] & 0x7f) << 24) | ((hash[offset + 1] & 0xff) << 16) | ((hash[offset + 2] & 0xff) << 8) | (hash[offset + 3] & 0xff)
var otp = binary % (10 ** digits)
return otp.toString().padStart(9, '000000000')
}En esta implementación se siguen los siguientes pasos:
Se define un tamaño de TOTP de 9 dígitos
Se obtiene el timestamp actual en intervalos de 30 segundos.
Se formatea la hora en hexadecimal, mayúscula y con un padding de 16 ceros
Se convierte la hora y la cláveme criptográfica a su buffer de bytes equivalente
Se genera un HMAC con el buffer de la clave criptografica y se actualiza con el buffer de la hora.
Se genera el hash de HMAC.
Se extraen 4 bytes de el buffer del HASH, se convierten a su equivalente a entero de 32 bits, se redondea a 9 dígitos y se añade un padding a la izquierda en caso que el valor sea menor a 9 bytes.