ID TECH
Contacto
Todos los artículos técnicos

Publicación técnica

Cómo analizar TLV en JavaScript

Una de las ventajas de las tarjetas con chip (ICC) es que los datos que emiten se entregan prácticamente siempre en un formato estándar, denominado BER-TLV. En lenguaje claro: Basic Encoding Rules, Tag-Length-Value (puede consultarse un artículo curioso pero informativo al respecto en aquí).

El formato BER-TLV es una de las codificaciones ASN.1 (Abstract Syntax Notation) definidas por ITU X.690, que es un conjunto de estándares muy antiguo, que se remonta a los albores de Internet.

Las tarjetas con chip utilizan el esquema TLV para codificar los datos de la tarjeta. En su forma más simple, el esquema Tag-Length-Value (etiqueta-longitud-valor) significa que, si tienes una etiqueta llamada (por ejemplo) «5A» cuyo valor son 8 octetos representados (por ejemplo) por los valores hexadecimales sucesivos «41 11 12 34 56 78 9A BC», la codificación TLV tendrá este aspecto: 5A084111123456789ABC, donde 5A es la etiqueta, 08 es la longitud y 4111123456789ABC es el valor.

EMVCo (el consorcio de emisores de tarjetas que está detrás de todo el sistema de las tarjetas con chip) define un buen número de etiquetas estándar para las transacciones con tarjeta chip. Por ejemplo, 5A siempre codifica el PAN (número de cuenta principal o número de tarjeta), 9F02 codifica el Importe Autorizado de una transacción, 5F2D codifica la Preferencia de Idioma, y así sucesivamente. La lista completa de etiquetas definidas por EMVco (y su significado) puede consultarse en https://www.eftlab.co.uk/index.php/site-map/knowledge-base/145-emv-nfc-tags.

Dado que los TLV codifican su propia longitud, analizar datos TLV debería ser pan comido, ¿verdad?

Pues sí. En su mayor parte. Más o menos.

Si todas las etiquetas tuvieran un identificador sencillo de un byte (como 5A), realmente sería superfácil analizar un flujo TLV. Pero el esquema TLV no resultaría muy útil si los identificadores solo pudieran tomar uno de los 256 valores posibles.

Para que los identificadores de etiqueta sean extensibles, las Basic Encoding Rules permiten etiquetas de varios bytes. Las reglas establecen que, si los 5 bits inferiores del primer byte de la etiqueta están a 1, siguen más bytes de identificador. En los bytes siguientes, el bit superior está a 1 si vienen más bytes, mientras que el bit superior es cero en el último byte. Así, por ejemplo, 5F24 es un identificador de etiqueta válido de 2 bytes, DFEF01 es una etiqueta válida de 3 bytes, y así sucesivamente.

EMVCo (que incorpora BER-TLV por referencia en el Libro 3, Anexo B, de las especificaciones EMV) también admite el concepto de etiquetas «envoltorio», para habilitar relaciones jerárquicas padre-hijo (o anidamiento) entre TLV. Según las reglas EMV, si el sexto bit del primer byte de una etiqueta está a 1, se dice que la etiqueta está «construida» (yo prefiero el término compuesta). Así, una etiqueta de 3 bytes FFEE01 podría utilizarse para envolver TLV (ficticios) 3F0188 y 3F025544 de la siguiente manera: FFEE01073F01883F025544. La etiqueta padre, FFEE01, tiene 7 bytes de datos, formados por un TLV de 3 bytes y un TLV de 4 bytes. Con este esquema, pueden anidarse grupos de etiquetas a la profundidad que se desee.

Conviene tener muy en cuenta que el byte de Longitud de un TLV también puede ser multibyte. Aquí, la regla de extensibilidad (tomada del Anexo B2 del Libro 3 de EMV) es:

Un byte de longitud con el bit superior a 1 significa que hay que tratar los 7 bits inferiores como la «longitud de la Longitud». Dicho de otro modo, un byte de Longitud de 0x82 significa que hay dos bytes de información de Longitud (en los dos bytes siguientes). En el TLV (ficticio) representado por 5F0F8103AABBCC, la etiqueta es 5F0F, la longitud de la Longitud es de un byte, la Longitud real es de 3 bytes y el Valor es AABBCC.

Claro como el agua, ¿verdad?

Así pues, sabiendo todo esto, podemos crear un analizador TLV totalmente general por descenso recursivo en unas 75 líneas de JavaScript, tal y como se muestra a continuación.

// «data» debería tener el aspecto «95050010203000…», etc.

// Es decir: TLV serializados como una única cadena larga.

// Se devuelve un objeto TLV. Úsalo para consultar Valores por nombre de Etiqueta.

// TLV[’95’] contendrá el valor de la etiqueta 95.

// TLV[‘9F26’] contendrá el valor de la etiqueta 9F26, etc.

La estrategia que utilizamos aquí es de una sencillez extrema:

Primero, ten a mano un diccionario amplio de identificadores de etiquetas, que contenga todas las etiquetas EMVCo conocidas (estándar del sector), más todas las etiquetas propietarias conocidas de ID TECH. A este diccionario lo llamamos _KnownTags, y puedes comprobar la existencia de un identificador como «5A» viendo si _KnownTags[ '5A' ] devuelve true.

A continuación: ¡a analizar!

Nuestro algoritmo de análisis es de lo más sencillo:

Lee dos nibbles cada vez en una variable tag y comprueba si la etiqueta existe en el diccionario. Todas las etiquetas del diccionario tendrán uno, dos o tres bytes de longitud, así que, si lees 6 nibbles sin encontrar una etiqueta conocida, basta con avanzar el marco de lectura 2 nibbles y continuar como si nada hubiera pasado (tras emitir un mensaje por consola que diga «Se esperaba una etiqueta y no se encontró»). Si quieres ponerte exquisito y lanzar una excepción aquí, puedes hacerlo, pero mi filosofía es que (dependiendo, claro está, de las circunstancias) un analizador, por defecto, debería ser tolerante a fallos (fail-soft), por si quieres seguir usando el resto de los datos analizados.

Una vez localizada una etiqueta, utiliza un método auxiliar (en este caso, una función interna llamada readData()) para pasar de la etiqueta, leer la Longitud y, con ella, leer el Valor. (Aquí hay que prestar atención al bit superior de la presunta Longitud, para ver si hay que aplicar la regla de extensibilidad de la longitud-de-la-Longitud mencionada anteriormente).

Coloca el Valor en un objeto de almacenamiento bajo una clave de consulta equivalente a tag.

Al final, devuelve el objeto de almacenamiento.

Probemos con un ejemplo real. Supongamos que tienes un Lector de tarjetas con chip ID TECH Augusta, y lo estás utilizando en modo teclado para capturar datos de Quick Chip. Los datos que se transmiten desde el dispositivo cuando insertas una tarjeta podrían tener este aspecto:

Se trata de un gran bloque de datos TLV que comienza con una etiqueta propietaria de ID TECH: DFEE25. (Puedes obtener más información sobre el significado de las etiquetas de ID TECH descargando la Guía de referencia de etiquetas TLV de ID TECH desde https://idtechproducts.atlassian.net/wiki/spaces/KB/overview.) No obstante, la mayoría de las etiquetas de este bloque son etiquetas estándar del sector EMVCo. Si asignamos el bloque, como cadena, a una variable JS llamada tagblock, y luego cargamos el analizador anterior y lo ejecutamos con parseTags( tagblock ), obtendremos un objeto con etiquetas y valores, así:

Algunas de estas etiquetas están vacías. Otras (como 9F27) contienen un valor de 00. Algunas están cifradas. Pero, en esencia, aquí tienes todas las etiquetas necesarias para ejecutar una transacción EMV.

¿Por qué usar JavaScript para analizar TLV? Bueno, si te diera la respuesta real, tendría que matarte arruinar la intriga que sin duda estás sintiendo ahora mismo si te insinuara formas de utilizar Node.js en el entorno de aplicaciones de pago, cómo comunicarte con lectores de tarjetas de crédito mediante JavaScript, cómo conectar con servidores de prueba back-end utilizando Servlets y AJAX, etc. Todo esto llegará muy pronto aquí mismo, así que añade este blog a tus favoritos y vuelve pronto.