JSON Web Signature ( JWS )

2019-12-23 JOSE

JSON Web Signature (JWS) representa una manera segura de proteger los mensajes utilizando firmas digitales o códigos de autenticación de mensajes (MACs) y el estándar JSON. No se debe confundir esta definición con sus contrapartes JWA (JSON Web Algorithms) que define los algoritmos de encriptado y JWE (JSON Web Encryption) que define el también contenido encriptado. Vamos a ver entonces de que se compone todo esto, y para no hacerlo tan técnico como la propia especificación (RFC 7515), vamos a tratar de definir los conceptos en el camino.

Componentes

Un JWS está compuesto por los siguientes elementos:

- JOSE Header
- JWS Payload
- JWS Signature

Adicional a esto, el JOSE Header a su vez está compuesto por la unión de los siguientes elementos:

- JWS Protected Header
- JWS Unprotected Header

Para representar un JWS existen básicamente dos caminos. El primero es llamado JWS Compact Serialization, y el segundo JWS JSON Serialization.

Serialización compacta

En la serialización compacta, se representa un JWS de la siguiente forma:

BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)

El operador || representa la concatenación de dos strings.

Serialización JSON

En la serialización JSON uno o ambos de los componentes JWS Protected Header y JWS Unprotected Header tiene que estar presente. De acuerdo a esto, la representación vendría dada por:

- "protected"
- "header"
- "payload"
- "signature"

El JSON resultante tendría una estructura similar a la siguiente.

{
    "protected": BASE64URL(UTF8(JWS Protected Header)),
    "header": "JWS Unprotected Header",
    "payload": BASE64URL(JWS Payload),
    "signature": BASE64URL(JWS Signature)
}

Cálculo de valores para un JWS

Hasta aquí todo puede parecer muy claro, sin embargo, te habrás preguntado acerca de las funciones BASE64URL y UTF8. A continuación la definición de cada una de ellas.

BASE64URL: Se refiere a una función que codifica un string en base64 usando un conjunto de caracteres seguros para envío por URL. Esto significa en pocas palabras, que después de codificado un string en base64, se debe omitir los caracteres =, y reemplazar / por _ y + por -.

UTF8: Representa un string de caracteres unicode de longitud variable definido como estándar en el RFC 3629.

Por otro lado, suele ser más claro representar un string en octetos para no tener discrepancias en cuanto a su contenido. Para entender un poco esto, basta observar la tabla ASCII, y reemplazar cada caracter con su repectivo octeto. Con todo esto, ya podemos proceder con el computo de un JWS.

Vamos a crear un JWS con el siguiente protected header, el cuál nos indica el tipo de estructura (JWT) y el algoritmo que se utilizará en la firma.

{"typ":"JWT", "alg":"HS256"}

Este string tiene la siguiente representación en octetos.

123, 34, 116, 121, 112,  34,
58,  34,  74,  87,  84,  34,
44,  13,  10,  32,  34,  97,
108, 103, 34,  58,  34,  72,
83,  50,  53,  54,  34,  125

Dado que vamos a utilizar la representación compacta serializada, se debe calcular la codificación segura BASE64URL con lo cual obtendríamos el siguiente string.

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9

Seguido a esto, vamos a utilizar el siguiente payload.

{"iss":"joe", "exp":1300819380, "http://example.com/is_root":true}

Su representación en octetos es la siguiente:

123,  34, 105, 115, 115,  34,  58,  34, 106, 111,
101,  34,  44,  13,  10,  32,  34, 101, 120, 112,
 34,  58,  49,  51,  48,  48,  56,  49,  57,  51,
 56,  48,  44,  13,  10,  32,  34, 104, 116, 116,
112,  58,  47,  47, 101, 120,  97, 109, 112, 108,
101,  46,  99, 111, 109,  47, 105, 115,  95, 114,
111, 111, 116,  34,  58, 116, 114, 117, 101, 125

Su codificación en BASE64URL seria la siguiente:

eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ

Hasta aquí solamente nos queda haciendo falta la firma. Para calcular esta firma hacemos uso de lo que ya tenemos, la entrada de la firma.

JWS Signing input: La entrada para el cálculo de la firma digital o el cálculo de los códigos MAC. Corresponde a la siguiente parte de la representación compacta.

BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload)

De acuerdo a esto, nuestro signing input sería el siguiente (con saltos de línea solo con fines de visualización):

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ

Para realizar la firma vamos a utilizar el algoritmo HMAC SHA-256 (como dijimos en el header) y una llave JWK sobre la cual no explicaremos mayor detalle ya que hace parte de otra especificación (RFC 7517). La llave que utilizaremos es la siguiente:

{"kty":"oct","k":"AyM1SysPpbyDfgZld3umj1qzKObwVMkoqQ-EstJQLr_T-1qS0gZH75aKtMN3Yj0iPS4hcgUuTwjAzZr1Z9CAow"}

Al correr el algoritmo de cifrado obtendremos un texto cifrado el cual tiene la siguiente representación en octetos:

116,  24, 223, 180, 151, 153, 224,  37,
 79, 250,  96, 125, 216, 173, 187, 186,
 22, 212,  37,  77, 105, 214, 191, 240,
 91,  88,   5,  88,  83, 132, 141, 121

Su codificación en BASE64URL seria la siguiente:

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

Con lo cual el JWS quedaría de la siguiente manera (con saltos de línea solo con fines de visualización):

eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9
.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ
.
mB92K27uhbUJU1p1r_wW1gFWFOEjXk 

Acerca de Darío Rivera

Author

Ingeniero de desarrollo en PlacetoPay , Medellín. Darío ha trabajado por más de 6 años en lenguajes de programación web especialmente en PHP. Creador del microframework DronePHP basado en Zend y Laravel.

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.