Git Product home page Git Product logo

mastercard / client-encryption-python Goto Github PK

View Code? Open in Web Editor NEW
16.0 15.0 9.0 168 KB

Library for Mastercard API compliant payload encryption/decryption.

Home Page: https://developer.mastercard.com/platform/documentation/security-and-authentication/securing-sensitive-data-using-payload-encryption/

License: MIT License

Python 100.00%
python python3 field-level-encryption fle encryption decryption openapi mastercard

client-encryption-python's Introduction

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:

  • A public request encryption certificate (aka Client Encryption Keys)
  • A private response decryption key (aka Mastercard Encryption Keys)

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.

  • encrypt_payload() usage:
config = JweEncryptionConfig(config_dictionary)
encrypted_request_payload = encrypt_payload(body, config)
  • decrypt_payload() usage:
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.

  • encrypt_payload() usage:
config = FieldLevelEncryptionConfig(config_dictionary)
encrypted_request_payload = encrypt_payload(body, config)
  • decrypt_payload() usage:
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()

client-encryption-python's People

Contributors

akshaykdubey avatar danny-gallagher avatar e129886 avatar ech0s7r avatar jaaufauvre avatar joseph-neeraj avatar karen-avetisyan-mc avatar nehasony avatar piyuhirpara avatar pqlmc avatar rajmohan-rajagopal avatar rfeelin avatar shimonar-mc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

client-encryption-python's Issues

[BUG] Client Encryption Lib doesn't accept decryption key

Bug Report Checklist

  • Have you provided a code sample to reproduce the issue?
  • Have you tested with the latest release to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?

Description
I am trying to invoke FLD Mastercard Submit API, I am using field level encryption lib in order to encrypt a the following payload on sandbox env and I am getting internal server error, and after checking with master card team they told me that they are getting an error: decryption key is missing in the request hoewever it exists in the config payload:
{ "paths": {"$": {"toEncrypt": {"$": "$"}, "toDecrypt": {"$": "$"}}}, "ivFieldName": "iv", "encryptedKeyFieldName": "encryptedKey", "encryptedValueFieldName": "encryptedValue", "oaepPaddingDigestAlgorithmFieldName": "oaepPaddingDigestAlgorithm", "oaepPaddingDigestAlgorithm": "SHA256", "dataEncoding": "hex", "decryptionKey": "Path/to/p12.file", "decryptionKeyPassword": "keystorepassword", "encryptionCertificate": "path/to/pemfile", "encryptionKeyFingerprintFieldName": "encryptionKeyFingerprint", "encryptionKeyFingerprint": "FINGEPRINT", "encryptionCertificateFingerprintFieldName": "encryptionCertificateFingerprint", "encryptionCertificateFingerprint": "FINGERPRINT", }

To Reproduce
Steps to reproduce the behavior.

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Additional context
I have used the same keys in the developed java application and it worked fine, however I didn't provide the decryption key in the config payload, as it is not required for this endpoints
https://sandbox.api.mastercard.com/fld/confirmed-frauds/mastercard-frauds

Related issues/PRs
Has a similar issue/PR been reported/opened before?
No
Suggest a fix/enhancement
If you can't fix the bug yourself, perhaps you can point to what might be causing the problem (line of code or commit), or simply make a suggestion.

INVALID_BODY_HASH

Hi,

I am trying to run MDES tokenize api against sandbox as below and getting error for hash mismatch. I am attaching the code so if any one can help debug. I am not sharing keys.

./tokenize.sh
{
"Errors": {
"Error": [
{
"Source": "Gateway",
"ReasonCode": "INVALID_BODY_HASH",
"Description": "The provided oauth_body_hash does not match the SHA256 hash of the request payload. Calculated: 8gvc07EIynBEDNsawysKl3cd+LbHDylpYlDiaX0Rbz8=, Received: 01NcCrM7J5FOzJrJiGs+5+UMnkZ5S5iveHoxY3inu+A=",
"Recoverable": false,
"Details": null
}
]
}
code.txt

}

[BUG] New vulnerability found in Cryptography library requires Cryptography version 42.0.0 which conflicts with your pyopenssl pinned versions

There has been a recent vulnerability found in Cryptography 41.0.0 which requires an upgrade to at least 42.0.0. However you have pinned pyopenssl to <=23.2.0 and Cryptography 42.0.0 requires pyopenssl 23.3.0. Additionally this mastercard library uses the load_pkcs12 functionality which has been deprecated in pyopenssl version 23.3.0. Due to these dependencies we have been unable to upgrade to the latest Cryptography version. I have also raised this issue with another of your python libraries: Mastercard/oauth1-signer-python#52

  • github link to Cryptography vulnerability: GithubLink
  • cve link to Cryptography vulnerability: CVELink

Please upgrade the mastercard client-encryption-python library to support openssl version 23.3.0

pyOpenSSL version doesn't support Cryptography 42.0.0

Running pipdeptree after install of the mastercard client-encryption-library shows that this library is pinning the pyopenssl version meaning that cryptography 42.0.0 is not supported

mastercard-client-encryption==1.9.0
├── pycryptodome [required: >=3.8.1, installed: 3.20.0]
├── pyOpenSSL [required: >=22.1.0,<=23.2.0, installed: 23.2.0]
│ └── cryptography [required: >=38.0.0,<42,!=40.0.1,!=40.0.0, installed: 41.0.7]
│ └── cffi [required: >=1.12, installed: 1.16.0]
│ └── pycparser [required: Any, installed: 2.21]

The issue seems to be on this line: https://github.com/Mastercard/client-encryption-python/blob/main/setup.py#L25 where the pyOpenSSL library version is restricted. Please update your library to support a minumum of pyOpenSSL 23.3.0 (so that Cryptography 42.0.0 can be installed)

publicKeyFingerprint SHA1 vs SHA256

We recently went live with Issuer-Initiated Digitization for MDES.

The implementation wasn't flawless though. One issue that came up - it seems like MC is expecting publicKeyFingerprint to be the SHA1 fingerprint of the certificate. However, the FieldLevelEncryptionConfig.__compute_fingerprint method only returns the SHA256 fingerprint.

In the "MDES Issuer Implementation Guide" (dated 5/12/2021) on page 101, the example payload shows a SHA1 fingerprint in publicKeyFingerprint.

I'm happy to submit a PR for this but wondering if any maintainers have feedback.

Insecure handling of padding during JWE decryption

The logic for padding removal from JWE encryption is incorrect. The current code merely strips unprintable characters regardless if they belong to the padding or not:

decoded_payload = ''.join(c for c in decrypted.decode() if c.isprintable())

For AES GCM this isn't an issue because the padding removal is handled automatically.
For modes that do care about padding (e.g. AES CBC) this is blindly removing characters under the assumption that A) they belong to the padding and B) that the padding is valid. This allows an attacker to spoof data much more easily because the padding is never validated and invalid JSON characters are being silently removed.

The pycryptodome library that this package uses contains padding utilities. I strongly recommend you use them.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.