Configurar notificaciones de cambio que incluyan datos del recurso

Microsoft Graph permite a las aplicaciones suscribirse a las notificaciones de cambio de recursos a través de webhooks. Puede configurar suscripciones para incluir los datos de recursos cambiados (como el contenido de un mensaje de chat de Microsoft Teams o información de presencia de Microsoft Teams) en las notificaciones de cambios. Su aplicación puede entonces ejecutar su lógica de negocio sin tener que hacer una llamada a la API por separado para obtener el recurso cambiado. Como resultado, la aplicación funciona mejor haciendo menos llamadas a la API, lo que es beneficioso en escenarios de gran escala.

La inclusión de datos de recursos como parte de las notificaciones de cambios requiere que implemente la siguiente lógica adicional para satisfacer los requisitos de acceso a los datos y de seguridad:

  • Administrar) las notificaciones especiales del ciclo de vida de suscripción (versión preliminar) para mantener un flujo ininterrumpido de datos. Microsoft Graph envía notificaciones del ciclo de vida de vez en cuando para requerir que una aplicación vuelva a autorizar, para asegurarse de que no hayan surgido problemas de acceso inesperados para incluir datos de recursos en las notificaciones de cambios.
  • Validar la autenticidad de las notificaciones de cambios como procedentes de Microsoft Graph.
  • Proporcionar una clave de encriptación pública y utilizar una clave privada para descifrar los datos de los recursos recibidos a través de las notificaciones de cambios.

Datos de recurso en la carga útil de notificación

En general, este tipo de notificaciones de cambio incluye los siguientes datos de recursos en la carga útil:

  • ID y tipo de la instancia de recurso modificada, devuelta en la propiedad resourceData.
  • Todos los valores de propiedad de esa instancia de recurso, cifrados como se especifica en la suscripción, se devuelven en la propiedad encryptedContent.
  • O, dependiendo del recurso, propiedades específicas devueltas en la propiedad resourceData. Para obtener sólo propiedades específicas, especifíquelas como parte de la URL del recurso en la suscripción, utilizando un $select parámetro.

Recursos admitidos

Actualmente, los recursos chatMessage y presencia (versión preliminar) de Microsoft Teams admiten notificaciones de cambios que incluyen datos del recurso. Específicamente, puede configurar una suscripción que se aplique a uno de los siguientes aspectos:

  • Mensajes nuevos o modificados en un canal de Teams específico:/teams/{id}/channels/{id}/messages
  • Mensajes nuevos o modificados en todos los canales de los equipos de toda la organización (inquilino): /teams/getAllMessages
  • Mensajes nuevos o modificados en un chat de Teams específico: /chats/{id}/messages
  • Mensajes nuevos o modificados en todos los chats de toda la organización (inquilino):/chats/getAllMessages
  • Actualización de la información de presencia del usuario: /communications/presences/{id}

Los recursos chatMessage y presencia (versión preliminar) admiten la inclusión de todas las propiedades de una instancia modificada en una notificación de cambios. No permiten devolver solo propiedades selectivas de la instancia.

Este artículo muestra un ejemplo de cómo suscribirse a notificaciones de cambios de mensajes en un canal de Teams, con cada notificaciones de cambios incluyendo los datos de recursos completos de la instancia modificada de chatMessage. Para obtener más información sobre las suscripciones de chatMessage, vea Obtener notificaciones de cambios para el chat y mensajes del canal.

Crear una suscripción

Para que los datos de recursos se incluyan en las notificaciones de modificación, debe especificar las siguientes propiedades, además de las que se especifican normalmente al crear una suscripción:

  • includeResourceData que debería establecerse en true para solicitar explícitamente datos de recursos.
  • encryptionCertificate que contiene sólo la clave pública que utiliza Microsoft Graph para cifrar los datos de recursos. Mantenga la clave privada correspondiente endescifrar el contenido.
  • encryptionCertificateId el cual es su propio identificador para el certificado. Utilice este ID. para que coincida en cada notificación de cambios, qué certificado utilizar para el descifrado.

Tenga en cuenta lo siguiente:

Ejemplo de solicitud de suscripción

El siguiente ejemplo se suscribe a los mensajes de canal que se crean o actualizan en Microsoft Teams.

POST https://graph.microsoft.com/v1.0/subscriptions
Content-Type: application/json
{
  "changeType": "created,updated",
  "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
  "resource": "/teams/{id}/channels/{id}/messages",
  "includeResourceData": true,
  "encryptionCertificate": "{base64encodedCertificate}",
  "encryptionCertificateId": "{customId}",
  "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
  "clientState": "{secretClientState}"
}

Respuesta a la suscripción

HTTP/1.1 201 Created
Content-Type: application/json

{
  "changeType": "created,updated",
  "notificationUrl": "https://webhook.azurewebsites.net/api/resourceNotifications",
  "resource": "/teams/{id}/channels/{id}/messages",
  "includeResourceData": true,
  "encryptionCertificateId": "{custom ID}",
  "expirationDateTime": "2019-09-19T11:00:00.0000000Z",
  "clientState": "{secret client state}"
}

Notificaciones del ciclo de vida de la suscripción (versión preliminar)

Algunos eventos pueden interferir con el flujo de notificaciones de cambios en una suscripción existente. La suscripción a las notificaciones del ciclo de vida le informan de las acciones que debe realizar para mantener un flujo ininterrumpido. A diferencia de una notificación de cambio de recurso que informa de un cambio en una instancia de recurso, una notificación de ciclo de vida se refiere a la suscripción en sí misma y a su estado actual en el ciclo de vida.

Para obtener más información sobre cómo recibir y responder las notificaciones del ciclo de vida (vista previa), consulte Reducir suscripciones faltantes y notificaciones de cambios (versión preliminar)

Validación de la autenticidad de las notificaciones

Las aplicaciones a menudo ejecutan la lógica de negocios basada en los datos de los recursos que se incluyen en las notificaciones de cambios. Es importante haber verificado primero la autenticidad de cada notificación de cambios. De lo contrario, un tercero podría suplantar electrónicamente su aplicación con notificaciones de cambios falsas y hacer que se ejecute incorrectamente la lógica de negocio, lo cual puede provocar un incidente de seguridad.

Para las notificaciones de cambios básicas que no contienen datos del recurso, puede confirmarlas basándose simplemente en el valor clientState como se describe en Procesamiento de las notificaciones de cambios. Esto es aceptable, ya que puede realizar llamadas de confianza posteriores a Microsoft Graph para obtener acceso a los datos de los recursos y, por lo tanto, el impacto de cualquier intento de falsificación es limitado.

Para las notificaciones de cambios que suministran datos de recursos, realice una validación más completa antes de procesar los datos.

En esta sección:

Token de validación en la notificación de cambios

Una notificación de cambios con datos de recursos contiene una propiedad adicional, validationTokens, que contiene una matriz de tokens JWT generados por Microsoft Graph. Microsoft Graph genera un único token para cada par de aplicación y espacio empresarial distinto para los que hay un elemento en la matriz de valores. Tenga en cuenta que las notificaciones de cambios pueden contener una mezcla de elementos para varias aplicaciones y espacios empresariales que se suscribieron utilizando la misma notificationUrl.

Nota: si está configurando notificaciones de cambio entregadas a través de Azure Event Hubs, Microsoft Graph no enviará los tokens de validación. Microsoft Graph no necesita validar la notificationUrl.

En el siguiente ejemplo, la notificación de cambios contiene dos elementos para la misma aplicación y para dos espacios empresariales diferentes, por lo que la matriz validationTokens contiene dos tokens que necesitan ser validados.

{
    "value": [
          {
            "subscriptionId": "76619225-ff6b-4489-96ca-4ef547e78b22",
      "tenantId": "84bd8158-6d4d-4958-8b9f-9d6445542f95",
            "changeType": "created",
            ...
          },
      {
            "subscriptionId": "e990d58f-fd93-40af-acf7-a7c907c5d8ea",
      "tenantId": "46d9e3bd-6309-4177-a016-b256a411e30f",
            "changeType": "created",
            ...
            }
    ],
    "validationTokens": [
        "eyJ0eXAiOiJKV1QiLCJhb...",
    "cGlkYWNyIjoiMiIsImlkc..."
    ]
}

Nota: para obtener una descripción completa de los datos que se envían cuando se entregan las notificaciones de cambios, vea changeNotificationCollection.

Cómo validar

Si no está familiarizado con la validación de tokens, vea Principios de validación de tokens para obtener información general. Utilice un SDK, como la biblioteca System.IdentityModel.Tokens.Jwt para .NET, o una biblioteca de terceros para una plataforma diferente.

Sea consciente de lo siguiente:

  • Asegúrese de enviar siempre un código de estado HTTP 202 Accepted como parte de la respuesta a la notificación de cambios.
  • Responda antes de validar la notificación de cambios (por ejemplo, si almacena notificaciones de cambios en colas para su procesamiento posterior) o después (si los procesa sobre la marcha), incluso si la validación falló.
  • Aceptar una notificación de cambios evita reintentos de entrega innecesarios y también evita que cualquier posible agente deshonesto descubra si han superado o no la validación. Siempre puede optar por ignorar una notificación de cambios no válida después de haberla aceptado.

En particular, realice la validación en todos los tokens JWT de la colección validationTokens. Si algún token falla, considere la notificación de cambios sospechosa e investigue más a fondo.

Utilice los siguientes pasos para validar los tokens y las aplicaciones que generan tokens:

  1. Validar que el token no ha caducado.

  2. Validar el token que no ha fue manipulado y fue emitido por la autoridad esperada, Plataforma de identidad de Microsoft:

    • Obtenga las claves de firma desde el punto final de la configuración común:https://login.microsoftonline.com/common/.well-known/openid-configuration. Esta configuración es almacenada en caché por su aplicación durante un período de tiempo. Tenga en cuenta que la configuración se actualiza con frecuencia ya que las claves de firma se giran diariamente.
    • Verifique la firma del token JWT usando esas teclas.

    No acepte token emitidos por ninguna otra autoridad.

  3. Valide que el token fue emitido para su aplicación que se está suscribiendo a notificaciones de cambio.

    Los siguientes pasos forman parte de la lógica de validación estándar en las bibliotecas de tokens de JWT y normalmente se pueden ejecutar como una llamada de función única.

    • Valida que el "público" del token coincida con el ID de tu aplicación.
    • Si tiene más de una aplicación recibiendo notificaciones de cambios, asegúrese de comprobar si hay varios ID.
  4. Crítico: Validar que la aplicación que generó el token representa al editor de notificación de cambios de Microsoft Graph.

    • Compruebe que la propiedad appid en el token coincide con el valor esperado de0bf30f3b-4a52-48df-9a82-234910c4a086.
    • Esto garantiza que las notificaciones de cambios no se envíen desde una aplicación diferente que no sea Microsoft Graph.

Ejemplo de token JWT

El siguiente es un ejemplo de las propiedades incluidas en el token JWT que se necesitan para la validación.

{
  // aud is your app's id 
  "aud": "8e460676-ae3f-4b1e-8790-ee0fb5d6148f",                           
  "iss": "https://sts.windows.net/84bd8158-6d4d-4958-8b9f-9d6445542f95/",
  "iat": 1565046813,
  "nbf": 1565046813,
  // Expiration date 
  "exp": 1565075913,                                                        
  "aio": "42FgYKhZ+uOZrHa7p+7tfruauq1HAA==",
  // appid represents the notification publisher and must always be the same value of 0bf30f3b-4a52-48df-9a82-234910c4a086 
  "appid": "0bf30f3b-4a52-48df-9a82-234910c4a086",                          
  "appidacr": "2",
  "idp": "https://sts.windows.net/84bd8158-6d4d-4958-8b9f-9d6445542f95/",
  "tid": "84bd8158-6d4d-4958-8b9f-9d6445542f95",
  "uti": "-KoJHevhgEGnN4kwuixpAA",
  "ver": "1.0"
}

Ejemplo: comprobar los tokens de validación

// add Microsoft.IdentityModel.Protocols.OpenIdConnect and System.IdentityModel.Tokens.Jwt nuget packages to your project
public async Task<bool> ValidateToken(string token, string tenantId, IEnumerable<string> appIds)
{
    var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
    var openIdConfig = await configurationManager.GetConfigurationAsync();
    var handler = new JwtSecurityTokenHandler();
    try
    {
    handler.ValidateToken(token, new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateIssuerSigningKey = true,
        ValidateLifetime = true,
        ValidIssuer = $"https://sts.windows.net/{tenantId}/",
        ValidAudiences = appIds,
        IssuerSigningKeys = openIdConfig.SigningKeys
    }, out _);
    return true;
    }
    catch (Exception ex)
    {
    Trace.TraceError($"{ex.Message}:{ex.StackTrace}");
    return false;
    }
}
private boolean IsValidationTokenValid(String[] appIds, String tenantId, String serializedToken) {
    try {
        JwkKeyResolver jwksResolver = new JwkKeyResolver();
        Jws<Claims> token = Jwts.parserBuilder()
        .setSigningKeyResolver(jwksResolver)
        .build()
        .parseClaimsJws(serializedToken);
        Claims body = token.getBody();
        String audience = body.getAudience();
        boolean isAudienceValid = false;
        for(String appId : appIds) {
        isAudienceValid = isAudienceValid || appId.equals(audience);
        }
        boolean isTenantValid = body.getIssuer().endsWith(tenantId + "/");
        return isAudienceValid  && isTenantValid; //nbf,exp and signature are already validated by library
    } catch (Exception e) {
        LOGGER.error("could not validate token");
        LOGGER.error(e.getMessage());
        return false;
    }
}
import jwt from 'jsonwebtoken';
import jkwsClient from 'jwks-rsa';

const client = jkwsClient({
  jwksUri: 'https://login.microsoftonline.com/common/discovery/v2.0/keys'
});

export function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    var signingKey = key.publicKey || key.rsaPublicKey;
    callback(null, signingKey);
  });
}

export function isTokenValid(token, appId, tenantId) {
  return new Promise((resolve) => {
    const options = {
      audience: [appId],
      issuer: [`https://sts.windows.net/${tenantId}/`]
    };
    jwt.verify(token, getKey, options, (err) => {
      if (err) {
        // eslint-disable-next-line no-console
        console.error(err);
        resolve(false);
      } else {
        resolve(true);
      }
    });
  });
}

Para que funcione el ejemplo de Java, también tendrá que implementar JwkKeyResolver.

package com.example.restservice;

import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwk.Jwk;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.SigningKeyResolverAdapter;
import java.security.Key;
import java.net.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class JwkKeyResolver extends SigningKeyResolverAdapter {
    private JwkProvider keyStore;
    private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
    public JwkKeyResolver() throws java.net.URISyntaxException, java.net.MalformedURLException {
        this.keyStore = new UrlJwkProvider((new URI("https://login.microsoftonline.com/common/discovery/keys").toURL()));
    }
    @Override
    public Key resolveSigningKey(JwsHeader jwsHeader, Claims claims) {
        try {
            String keyId = jwsHeader.getKeyId();
            Jwk pub = keyStore.get(keyId);
            return pub.getPublicKey();
        } catch (Exception e) {
            LOGGER.error(e.getMessage());
            return null;
        }
    }
}

Descifrar datos de recursos de notificaciones de cambios

La propiedad resourceData de una notificación de cambios incluye sólo el ID. básico y la información de tipo de una instancia de recurso. La propiedad encryptedData contiene todos los datos de recursos, cifrados por Microsoft Graph utilizando la clave pública proporcionada en la suscripción. La propiedad también contiene los valores necesarios para la verificación y descifrado. Esto se hace para aumentar la seguridad de los datos de los clientes a los que se accede mediante notificaciones de cambios. Es su responsabilidad asegurar la clave privada para asegurar que los datos de los clientes no puedan ser descifrados por un tercero, incluso si logran interceptar las notificaciones de cambios originales.

En esta sección:

Administración de claves de cifrado

  1. Obtener un certificado con un par de claves asimétricas.

    • Puede autofirmar el certificado, ya que Microsoft Graph no comprueba el emisor del certificado y usa la clave pública solo para el cifrado.

    • Utilice Azure Key Vault como la solución para crear, rotar y gestionar certificados de forma segura. Asegúrese de que las teclas cumplen los siguientes criterios:

      • La clave debe ser de tipo RSA
      • El tamaño de la clave debe estar entre 2048 y 4096 bits
  2. Exportar el certificado en formato X.509 con codificación base64, y incluir sólo la clave pública.

  3. Al crear una suscripción:

    • Proporcione el certificado en la propiedad encryptionCertificate, utilizando el contenido con codificación base64 en el que se exportó el certificado.

    • Proporcione su propio identificador en la propiedad encryptionCertificateId.

      Este identificador le permite hacer coincidir sus certificados con las notificaciones de cambios que recibe y recuperar los certificados de su almacén de certificados. El identificador puede tener hasta 128 caracteres.

  4. Administre la clave privada de forma segura, de modo que el código de procesamiento de notificaciones de cambios pueda acceder a la clave privada para descifrar los datos de los recursos.

Claves rotativas

Para minimizar el riesgo de que una clave privada se vea comprometida, cambie periódicamente sus claves asimétricas. Siga estos pasos para introducir un nuevo par de claves:

  1. Obtener un nuevo certificado con un nuevo par de claves asimétricas. Utilícelo para todas las suscripciones nuevas que se estén creando.

  2. Actualice las suscripciones existentes con la nueva clave de certificado.

    • Haga esto como parte de la renovación regular de la suscripción.
    • O bien, enumere todas las suscripciones y proporcione la clave. Utilice la operación revisión en la suscripción y actualice las propiedades encryptionCertificate y encryptionCertificateId.
  3. Tenga en cuenta lo siguiente:

    • Durante un tiempo, el certificado antiguo puede seguir utilizándose para el cifrado. Su aplicación debe tener acceso tanto a los certificados antiguos como a los nuevos para poder descifrar el contenido.
    • Utilice la propiedad encryptionCertificateId en cada notificación de cambios para identificar la clave correcta a utilizar.
    • Descarte el certificado antiguo sólo cuando no haya visto ninguna notificación de cambios reciente que haga referencia al mismo.

Descifrar los datos de recursos

Para optimizar el rendimiento, Microsoft Graph utiliza un proceso de cifrado de dos pasos:

  • Genera una clave simétrica de un solo uso y la utiliza para encriptar datos de recursos.
  • Utiliza la clave pública asimétrica (que usted proporcionó al suscribirse) para cifrar la clave simétrica y la incluye en cada notificación de cambios de esa suscripción.

Suponga siempre que la clave simétrica es diferente para cada elemento de la notificación de cambios.

Para descifrar los datos de recursos, su aplicación debería realizar los pasos invertidos, utilizando las propiedades bajo encryptedContent en cada notificación de cambios:

  1. Utilice la propiedad encryptionCertificateId para identificar el certificado a utilizar.

  2. Inicialice un componente criptográfico RSA (como el .NET RSACryptoServiceProvider) con la clave privada.

  3. Descifre la clave simétrica entregada en la propiedad dataKey de cada elemento de la notificación de cambios.

    Utilice Optimal Asymmetric Encryption Padding (OAEP) para el algoritmo de descifrado.

  4. Utilizar la clave simétrica para calcular la firma HMAC-SHA256 del valor en los datos.

    Compárelo con el valor en dataSignature. Si no coinciden, asuma que la carga útil ha sido manipulada y no la descifre.

  5. Utilice la clave simétrica con un Advanced Encryption Standard (AES) (como el .NET AesCryptoServiceProvider) para descifrar el contenido de los datos.

    • Utilice los siguientes parámetros de descifrado para el algoritmo AES:

      • Relleno: PKCS7
      • Modo de cifrado: CSC
    • Ajuste el "vector de inicialización" copiando los primeros 16 bytes de la clave simétrica utilizada para el descifrado.

  6. El valor descifrado es una cadena JSON que representa la instancia del recurso en la notificación de cambios.

Ejemplo: descifrar una notificación con datos de recursos cifrados

El siguiente es un ejemplo de notificación de cambios que incluye valores de propiedad cifrados de una instancia de chatMessage en un mensaje de canal. La instancia se especifica por el valor @odata.id.

{
    "value": [
        {
            "subscriptionId": "76222963-cc7b-42d2-882d-8aaa69cb2ba3",
            "changeType": "created",
            // Other properties typical in a resource change notification
            "resource": "teams('d29828b8-c04d-4e2a-b2f6-07da6982f0f0')/channels('19:f127a8c55ad949d1a238464d22f0f99e@thread.skype')/messages('1565045424600')/replies('1565047490246')",
            "resourceData": {
                "id": "1565293727947",
                "@odata.type": "#Microsoft.Graph.ChatMessage",
                "@odata.id": "teams('88cbc8fc-164b-44f0-b6a6-b59b4a1559d3')/channels('19:8d9da062ec7647d4bb1976126e788b47@thread.tacv2')/messages('1565293727947')/replies('1565293727947')"
            },
            "encryptedContent": {
                "data": "{encrypted data that produces a full resource}",
        "dataSignature": "<HMAC-SHA256 hash>",
                "dataKey": "{encrypted symmetric key from Microsoft Graph}",
                "encryptionCertificateId": "MySelfSignedCert/DDC9651A-D7BC-4D74-86BC-A8923584B0AB",
                "encryptionCertificateThumbprint": "07293748CC064953A3052FB978C735FB89E61C3D"
            }
        }
    ],
    "validationTokens": [
        "eyJ0eXAiOiJKV1QiLCJhbGciOiJSU..."
    ]
}

Nota: para obtener una descripción completa de los datos que se envían cuando se entregan las notificaciones de cambios, vea changeNotificationCollection.

Esta sección contiene algunos fragmentos de código útiles que utilizan C# y .NET para cada etapa de desencriptación.

Descifrar la clave simétrica

// Initialize with the private key that matches the encryptionCertificateId.
RSACryptoServiceProvider rsaProvider = ...;        
byte[] encryptedSymmetricKey = Convert.FromBase64String(<value from dataKey property>);

// Decrypt using OAEP padding.
byte[] decryptedSymmetricKey = rsaProvider.Decrypt(encryptedSymmetricKey, fOAEP: true);

// Can now use decryptedSymmetricKey with the AES algorithm.
String storename = ""; //name/path of the jks store
String storepass = ""; //password used to open the jks store
String alias = ""; //alias of the certificate when store in the jks store, should be passed as encryptionCertificateId when subscribing and retrieved from the notification
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(storename), storepass.toCharArray());
Key asymmetricKey = ks.getKey(alias, storepass.toCharArray());
byte[] encryptedSymetricKey = Base64.decodeBase64("<value from dataKey property>");
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher.init(Cipher.DECRYPT_MODE, asymmetricKey);
byte[] decryptedSymmetricKey = cipher.doFinal(encryptedSymetricKey);
// Can now use decryptedSymmetricKey with the AES algorithm.
const base64encodedKey = 'base 64 encoded dataKey value';
const asymetricPrivateKey = 'pem encoded private key';
const decodedKey = Buffer.from(base64encodedKey, 'base64');
const decryptedSymetricKey = crypto.privateDecrypt(asymetricPrivateKey, decodedKey);
// Can now use decryptedSymmetricKey with the AES algorithm.

Comparar la firma de datos utilizando HMAC-SHA256

byte[] decryptedSymmetricKey = <the aes key decrypted in the previous step>;
byte[] encryptedPayload = <the value from the data property, still encrypted>;
byte[] expectedSignature = <the value from the dataSignature property>;
byte[] actualSignature;

using (HMACSHA256 hmac = new HMACSHA256(decryptedSymmetricKey))
{
    actualSignature = hmac.ComputeHash(encryptedPayload);
}
if (actualSignature.SequenceEqual(expectedSignature))
{
    // Continue with decryption of the encryptedPayload.
}
else
{
    // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}
byte[] decryptedSymmetricKey = "<the aes key decrypted in the previous step>";
byte[] decodedEncryptedData = Base64.decodeBase64("data property from encryptedContent object");
Mac mac = Mac.getInstance("HMACSHA256");
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "HMACSHA256");
mac.init(skey);
byte[] hashedData = mac.doFinal(decodedEncryptedData);
String encodedHashedData = new String(Base64.encodeBase64(hashedData));
if (comparisonSignature.equals(encodedHashedData))
{
    // Continue with decryption of the encryptedPayload.
}
else
{
    // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}
const decryptedSymetricKey = []; //Buffer provided by previous step
const base64encodedSignature = 'base64 encodded value from the dataSignature property';
const hmac = crypto.createHmac('sha256', decryptedSymetricKey);
hmac.write(base64encodedPayload, 'base64');
if(base64encodedSignature === hmac.digest('base64'))
{
    // Continue with decryption of the encryptedPayload.
}
else
{
    // Do not attempt to decrypt encryptedPayload. Assume notification payload has been tampered with and investigate.
}

Descifrar el contenido de los datos del recurso

AesCryptoServiceProvider aesProvider = new AesCryptoServiceProvider();
aesProvider.Key = decryptedSymmetricKey;
aesProvider.Padding = PaddingMode.PKCS7;
aesProvider.Mode = CipherMode.CBC;

// Obtain the intialization vector from the symmetric key itself.
int vectorSize = 16;
byte[] iv = new byte[vectorSize];
Array.Copy(decryptedSymmetricKey, iv, vectorSize);
aesProvider.IV = iv;

byte[] encryptedPayload = Convert.FromBase64String(<value from dataKey property>);

string decryptedResourceData;
// Decrypt the resource data content.
using (var decryptor = aesProvider.CreateDecryptor())
{
  using (MemoryStream msDecrypt = new MemoryStream(encryptedPayload))
  {
      using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
      {
          using (StreamReader srDecrypt = new StreamReader(csDecrypt))
          {
              decryptedResourceData = srDecrypt.ReadToEnd();
          }
      }
  }
}

// decryptedResourceData now contains a JSON string that represents the resource.
SecretKey skey = new SecretKeySpec(decryptedSymmetricKey, "AES");
IvParameterSpec ivspec = new IvParameterSpec(Arrays.copyOf(decryptedSymmetricKey, 16));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skey, ivspec);
String decryptedResourceData = new String(cipher.doFinal(Base64.decodeBase64(encryptedData)));
const base64encodedPayload = 'base64 encoded value from data property';
const decryptedSymetricKey = []; //Buffer provided by previous step
const iv = Buffer.alloc(16, 0);
decryptedSymetricKey.copy(iv, 0, 0, 16);
const decipher = crypto.createDecipheriv('aes-256-cbc', decryptedSymetricKey, iv);
let decryptedPayload = decipher.update(base64encodedPayload, 'base64', 'utf8');
decryptedPayload += decipher.final('utf8');

Consulte también