Message level encryption

Certain processes and workflows using Rabo Identity Services APIs contain highly sensitive PII data. To protect this data, Rabobank uses message level encryption.

Message-Level Encryption (MLE) enhances security by ensuring that sensitive data remains encrypted during transit and it is implemented as an additional layer on top of standard transport layer security (TLS).

This guide explains how to provide the correct input for encryption, how encryption is performed, and how to decrypt the received content.

Follow the listed steps to use message level encryption using PKI:

Required Input

To receive an encrypted response, API clients are required to include a JSON Web Key (JWK) in the mandatory X-Encryption-Key request header as part of any API invocation, where message level encryption is applied.

Consumers of the API are responsible for generating the key(s) and storing the private part of the key securely.

Rabobank Identity Services APIs supports RSA and EC key types, defined using the kty attribute.

The minimum required key configuration is as below:

Applying RSA keys

FieldValueNotes
ktyRSARequired
algRSA-OAEP, RSA-OAEP-256Required
useencrequired
kidcase-sensitive string identifying the keyRecommended
epublic exponentRequired
nmodulusRequired

Applying EC keys

FieldValueNotes
ktyECRequired
algECDH-ESRequired
crvP-256, P-384, P-521Required
useencRequired
kidcase-sensitive string identifying the keyRecommended
xx coordinateRequired
yy coordinateRequired

Valid RSA X-Encryption-Key header value

{
"kty": "RSA",
"e": "AQAB",
"use": "enc",
"kid": "gYtIlsCr3gGSdmY1Uj0q01ud0bDzSutAPHO9wHens4s",
"alg": "RSA-OAEP-256",
"n":
"ufzviUV7l_DfVjHOegUCCAfC2jEPcHqmlbaO9ksHBGeYmpqQfp37pprcfUytVtQkvLoPOyX_0XwqrirQ4BI5ApfbxqQVuamOMd_qFzPlHqusnrbZeFIMDm0wnNM00Wezh3BQ4Jz958cqaS30mpXWec-UXfmkVOifbzjc2vcYxYY0_6-9-QTjDbpy_R7yx_FvECQO07qh7axTOwRdgNoN5mXVLs1in1Rx_QBfhcaxkTnN3R5nGaFqhiDHX2OWIfaiscv8QHqi0wpLI7du74nAmENwpFjLVHrC5D8KrD0QUN0IW8pr-wiJ8-sGmehBGDDUs6iPUNyy46Edmh8beavWKQ"
}

Setting the header

Include the JWK mapping the public part of the keyset in the X-Encryption-Key header in all API requests where encryption is required.

Example request:

GET /api/encrypted-resource HTTP/1.1
X-Encryption-Key: {"kty":"RSA","e":"AQAB","n":"base64url-encoded-modulus","alg":"RSA-OAEP-256","use":"enc","kid":"unique-key-id"}

Encryption Process

The API client sends a request with the X-Encryption-Key header containing its public key:

  • Rabobank validates the provided X-Encryption-Key header.
  • If the validation is found correct, message level encryption is applied on the response body.

After request processing, the API response payload is encrypted by the Rabobank Identity Services API platform using the provided public key and returned to the consumer API client as a JWE(JSON Web Encryption) token with the Content-Type: application/jose, which must be decrypted using a JWE-compatible library.

See RFC7516 - JSON Web Encryption (JWE)

Decrypting the Response

To decrypt the response, use the private key corresponding to the public key sent in X-Encryption-Key 

Response example with message level encryption

HTTP/1.1 200 OK
Content-Type: application/jose
eyJraWQiOiJ1R0VIUWdBeUFfdFNBQWhhcUNRSTVJVHBrcWdhU2xCQXVhTklWQzJGRUU0IiwiZW5jIjoiQTI1NkdDTSIsImFsZyI6IlJTQS1PQUVQLTI1NiJ9.2l_oIXvIuNoDVTi42YKS9lnefBMmigQFuukZYZ13Z3osyUaU0dLhycn-c0mY8S4_oGNK8ov-eibVyNPE5WMbfZrvFRXQ2VyLeodoDDRm2IdkapyCQB7TDc2G0PHXCWofkbOvpKbUp43GyOz3MJ3bOzUdRfMg3sLwrbXT6CGs-RW1OfVtdEO4BsZywDOKvJ43T7VVQpcDjdyKVuJsEpe7E1kieD2X4P_sHF84T_uP5Wwrh5qVYMirnD4fhS4dYvP6ychwuQ4WlEE6s-YfCO4Gdv5ZCJ8n3OhL83vi8V4u4haTivE_psNOPKGcC-q_05TiNy-92jqha7qq-eMNSkoYJw.XODDXTTol1YqA3mO.tgP2l8HhdRDrLOaM6kxublpVn7n2RyVn3MkFXVfWDpCc9ZuL-WR3YpRGanYIn62qpmMCcC6ekqbSpK8MCLhO5tQ5xSWa2GeVbkWGjt3RhQCZfBqfS19_sX_nFcMgSuqOz5cUtiCdb2POsPNReY-Q2V24gBj1TKifoMby3JPxcsg5WfN3wIbq8QExa-dPB2RFQDVRVj-GOZR9TVIKd4D9gB_Ys8kcsvFlBEWmDLXPsxqiqf274f8i4dzQb_sSuDQ4gsLpQz5W82u_mxL2e372L9-wK-qh4sV8mwaeFc-GmoctOxr0OaSJRy0OfB04ODJDF201MmA-cB-RFy3j8yJRAOx2hcK0Z5tF35l9HJ8fhzGoHQsPOQpgyB5RO1L-GW3JlgeyIBjz-siLmzAhcFdkmzt7sA6pb0lGrlb_yQ2gIsvIajQiELLeKmn-9aEVlBdz3yyCbLHmrlxWv2RJuNP_GiMJwNw0_i6yzJb5NyAeB57y613k8hKxSVeK1TeQKrS8CiVUgikY9D93kUarIuSvzh9pAU2TBAoBlbG_LtbnKiRA94SdSYPR6B2EEes2TI-qsuEU3MRR1o4UyMQ1jVWw9KWYaWIn2cgGO0KC_xawzsPxQYhhP_LqgVXYaH935maOMTRWAizsSC1q6U7vpl693HdqB9agL-kWiNiAb8p0WBxswDodr7JkfKJZ2YRKmvg2lO-NEL1qvYyhUK7sqrReptaglp-fsY-WrPCK3PlHZCxPPXd1aRLfNBq68-p9TxvclavWV7bNZBfp50_9QlhO2hzKTlFWZgd8eKuSooHHf7dOY86LSyD3XLNPxpEbOkoSp8d2L1hY8w8.h1M06f8i3sqzwfDnUGmRvA

Generic example of how decryption can be implemented:

FUNCTION decryptJWE(jweToken):

  1. Parse the JWE token to extract encryption details.
  2. Load the private key securely.
  3. Use a cryptographic library to decrypt the JWE using the private key.
  4. Retrieve the decrypted payload.
  5. CHECK the expected content type on the API documentation:
    1. IF text-based (JSON, XML) → Parse, deserialize or return as text.
    2. IF binary (file, image, PDF) → Handle as binary stream.
  6. Return the appropriate format to the caller.

Security Considerations

  • Key Rotation: Regularly update encryption keys to maintain security.
  • Secure Storage: Never expose or log private keys.