Mastercard / client-encryption-python

Library for Mastercard API compliant payload encryption/decryption.
https://developer.mastercard.com/platform/documentation/security-and-authentication/securing-sensitive-data-using-payload-encryption/
MIT License
17 stars 10 forks source link
decryption encryption field-level-encryption fle mastercard openapi python python3

client-encryption-python

Table of Contents

Overview

This is the Python version of the Mastercard compliant payload encryption/decryption.

Compatibility

Python 3.8+

References

Versioning and Deprecation Policy

Usage

Prerequisites

Before using this library, you will need to set up a project in the Mastercard Developers Portal.

As part of this set up, you'll receive:

Installation

If you want to use mastercard-client-encryption with Python, it is available through PyPI:

Adding the library to your project Install the library by pip:

pip install mastercard-client-encryption

Or clone it from git:

git clone https://github.com/Mastercard/client-encryption-python.git

and then execute from the repo folder:

python3 setup.py install

You can then use it as a regular module:

# Mastercard Encryption/Decryption
from client_encryption.field_level_encryption_config import FieldLevelEncryptionConfig
from client_encryption.field_level_encryption import encrypt_payload, decrypt_payload
# JWE Encryption/Decryption
from client_encryption.jwe_encryption_config import JweEncryptionConfig
from client_encryption.jwe_encryption import encrypt_payload, decrypt_payload

Performing Payload Encryption and Decryption

This library supports two types of encryption/decryption, both of which support field level and entire payload encryption: JWE encryption and what the library refers to as Field Level Encryption (Mastercard encryption), a scheme used by many services hosted on Mastercard Developers before the library added support for JWE.

JWE Encryption and Decryption

Introduction

This library uses JWE compact serialization for the encryption of sensitive data. The core methods responsible for payload encryption and decryption are encrypt_payload and decrypt_payload in the jwe_encryption module.

config = JweEncryptionConfig(config_dictionary)
encrypted_request_payload = encrypt_payload(body, config)
config = JweEncryptionConfig(config_dictionary)
decrypted_response_payload = decrypt_payload(body, config)
Configuring the JWE Encryption

jwe_encryption needs a config dictionary to instruct how to decrypt/decrypt the payloads. Example:

{
  "paths": {
    "$": {
      "toEncrypt": {
          "path.to.foo": "path.to.encryptedFoo"
      },
      "toDecrypt": {
          "path.to.encryptedFoo": "path.to.foo"
      }
    }
  },
  "encryptedValueFieldName": "encryptedData",
  "encryptionCertificate": "./path/to/public.cert",
  "decryptionKey": "./path/to/your/private.key",
}

The above can be either stored to a file or passed to 'JweEncryptionConfig' as dictionary:

config_dictionary = {
                        "paths": {…},
                        …
                        "decryptionKey": "./path/to/your/private.key"
                    }

config = JweEncryptionConfig(config_dictionary)

config_file_path = "./config.json"
config = JweEncryptionConfig(config_file_path)
Performing JWE Encryption

Call jwe_encryption.encrypt_payload() with a JSON (dict) request payload, and optional params object.

Example using the configuration above:

from client_encryption.session_key_params import SessionKeyParams

payload = {
  "path": {
    "to": {
      "foo": {
        "sensitiveField1": "sensitiveValue1",
        "sensitiveField2": "sensitiveValue2"
      }
    }
  }
}

params = SessionKeyParams.generate(conf) # optional
request_payload = encrypt_payload(payload, config, params)

Output:

{
  "path": {
    "to": {
      "encryptedFoo": {
        "encryptedValue": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM(...)==.Y+oPYKZEMTKyYcSIVEgtQw=="
      }
    }
  }
}
Performing JWE Decryption

Call jwe_encryption.decrypt_payload() with a JSON (dict) encrypted response payload.

Example using the configuration above:

response = {
  "path": {
    "to": {
      "encryptedFoo": {
        "encryptedValue": "eyJraWQiOiI3NjFiMDAzYzFlYWRlM(...)==.Y+oPYKZEMTKyYcSIVEgtQw=="
      }
    }
  }
}

response_payload = decrypt_payload(response, config)

Output:

{
  "path": {
    "to": {
      "foo": {
        "sensitiveField1": "sensitiveValue1",
        "sensitiveField2": "sensitiveValue2"
      }
    }
  }
}

Mastercard Encryption and Decryption

Introduction

The core methods responsible for payload encryption and decryption are encrypt_payload and decrypt_payload in the field_level_encryption module.

config = FieldLevelEncryptionConfig(config_dictionary)
encrypted_request_payload = encrypt_payload(body, config)
config = FieldLevelEncryptionConfig(config_dictionary)
decrypted_response_payload = decrypt_payload(body, config)
Configuring the Mastercard Encryption

field_level_encryption needs a config dictionary to instruct how to decrypt/decrypt the payloads. Example:

{
  "paths": {
    "$": {
      "toEncrypt": {
          "path.to.foo": "path.to.encryptedFoo"
      },
      "toDecrypt": {
          "path.to.encryptedFoo": "path.to.foo"
      }
    }
  },
  "ivFieldName": "iv",
  "encryptedKeyFieldName": "encryptedKey",
  "encryptedValueFieldName": "encryptedData",
  "dataEncoding": "hex",
  "encryptionCertificate": "./path/to/public.cert",
  "decryptionKey": "./path/to/your/private.key",
  "oaepPaddingDigestAlgorithm": "SHA256"
}

The above can be either stored to a file or passed to 'FieldLevelEncryptionConfig' as dictionary:

config_dictionary = {
                        "paths": {…},
                        …
                        "decryptionKey": "./path/to/your/private.key",
                        "oaepPaddingDigestAlgorithm": "SHA256"
                    }

config = FieldLevelEncryptionConfig(config_dictionary)

config_file_path = "./config.json"
config = FieldLevelEncryptionConfig(config_file_path)
Performing Mastercard Encryption

Call field_level_encryption.encrypt_payload() with a JSON (dict) request payload, and optional params object.

Example using the configuration above:

from client_encryption.session_key_params import SessionKeyParams

payload = {
  "path": {
    "to": {
      "foo": {
        "sensitiveField1": "sensitiveValue1",
        "sensitiveField2": "sensitiveValue2"
      }
    }
  }
}

params = SessionKeyParams.generate(conf) # optional
request_payload = encrypt_payload(payload, config, params)

Output:

{
    "path": {
        "to": {
            "encryptedFoo": {
                "iv": "7f1105fb0c684864a189fb3709ce3d28",
                "encryptedKey": "67f467d1b653d98411a0c6d3c…ffd4c09dd42f713a51bff2b48f937c8",
                "encryptedData": "b73aabd267517fc09ed72455c2…dffb5fa04bf6e6ce9ade1ff514ed6141",
                "publicKeyFingerprint": "80810fc13a8319fcf0e2e…82cc3ce671176343cfe8160c2279",
                "oaepHashingAlgorithm": "SHA256"
            }
        }
    }
}
Performing Mastercard Decryption

Call field_level_encryption.decrypt_payload() with a JSON (dict) encrypted response payload.

Example using the configuration above:

response = {
  "path": {
    "to": {
      "encryptedFoo": {
        "iv": "e5d313c056c411170bf07ac82ede78c9",
        "encryptedKey": "e3a56746c0f9109d18b3a2652b76…f16d8afeff36b2479652f5c24ae7bd",
        "encryptedData": "809a09d78257af5379df0c454dcdf…353ed59fe72fd4a7735c69da4080e74f",
        "oaepHashingAlgorithm": "SHA256",
        "publicKeyFingerprint": "80810fc13a8319fcf0e2e…3ce671176343cfe8160c2279"
      }
    }
  }
}

response_payload = decrypt_payload(response, config)

Output:

{
  "path": {
    "to": {
      "foo": {
        "sensitiveField1": "sensitiveValue1",
        "sensitiveField2": "sensitiveValue2"
      }
    }
  }
}

Integrating with OpenAPI Generator API Client Libraries

OpenAPI Generator generates API client libraries from OpenAPI Specs. It provides generators and library templates for supporting multiple languages and frameworks.

The client-encryption-python library provides a method you can use to integrate the OpenAPI generated client with this library:

from client_encryption.api_encryption import add_encryption_layer

config = {
  "paths": {
    "$": {
      …
    }
  },
  "encryptionCertificate": "path/to/cert.pem",
  …
  "decryptionKey": "path/to/to/key.pem"
}

add_encryption_layer(api_client, config)

Alternatively you can pass the configuration by a json file:

from client_encryption.api_encryption import add_encryption_layer

add_encryption_layer(api_client, "path/to/my/config.json")

This method will add the Mastercard/JWE encryption in the generated OpenApi client, taking care of encrypting request and decrypting response payloads, but also of updating HTTP headers when needed, automatically, without manually calling encrypt_payload()/decrypt_payload() functions for each API request or response.

OpenAPI Generator

OpenAPI client can be generated, starting from your OpenAPI Spec using the following command:

openapi-generator-cli generate -i openapi-spec.yaml -l python -o out

The client library will be generated in the out folder.

See also:

Usage of the api_encryption.add_encryption_layer:

To use it:

  1. Generate the OpenAPI client

  2. Import the mastercard-client-encryption module and the generated OpenAPI client

    from client_encryption.api_encryption import add_encryption_layer
    from openapi_client.api_client import ApiClient # import generated OpenAPI client
  3. Add the encryption layer to the generated client:

    # Create a new instance of the generated client
    api_client = ApiClient()
    # Enable encryption
    add_encryption_layer(api_client, "path/to/my/config.json")
  4. Use the ApiClient instance with Encryption enabled:

    Example:

    request_body = {…}
    response = MyServiceApi(api_client).do_some_action_post(body=request_body)
    # requests and responses will be automatically encrypted and decrypted
    # accordingly with the configuration object used
    
    # … use the (decrypted) response object here …
    decrypted = response.json()
    
Integrating with mastercard-client-encryption module:

In order to use both signing and encryption layers, a defined order is required as signing library should calculate the hash of the encrypted payload. According to the above the signing layer must be applied first in order to work as inner layer. The outer layer - encryption - will be executed first, providing the signing layer the encrypted payload to sign.

  1. Generate the OpenAPI client

  2. Import both mastercard-client-encryption and mastercard-client-encryption modules and the generated OpenAPI client

    from oauth1.signer_interceptor import add_signing_layer
    from client_encryption.api_encryption import add_encryption_layer
    from openapi_client.api_client import ApiClient # import generated OpenAPI client
  3. Add the authentication layer to the generated client:

    # Create a new instance of the generated client
    api_client = ApiClient()
    
    # Enable authentication
    add_signing_layer(api_client, key_file, key_password, consumer_key)
  4. Then add the encryption layer:

    add_encryption_layer(api_client, "path/to/my/config.json")
  5. Use the ApiClient instance with Authentication and Encryption both enabled:

    response = MyServiceApi(api_client).do_some_action_post(body=request_body)
    decrypted = response.json()