Skip to Content
Technical Articles
Author's profile photo Yann Miquel

How to consume the Credential store with python

For those interested in a secure repository for passwords and keys for applications running on SAP Business Technology Platform, the Credential store is the way to go. This blog describes very well how to set up the service, so I won’t repeat these steps here.

The aim of this post is to explain how to consume this service with Python 3. The blog mentioned above describes how to consume the service with Node.js. You can find in the official SAP API documention how to consume it with Java, Javascript, Swift, Curl, ABAP and SAPUI5. A GoLang sample is also available in this other documentation. Almost all languages but Python.

The context of this work is the management the HANA Cloud instance service (start and stop). For this task, one need to use the credentials of a Cloud foundry manager and we don’t want these written in plain text. Since updating a Cloud Foundry service with Python would be another topic, I will only expose here how to consume the Credential store.

The core python libraries are requests and python-jose, which have Apache 2-0 and MIT license respectively.

All of what follow could be written nicely in a function but I want to emphasise what do what.

Initialisation

After creating the service mycreds (free plan), we add in the namespace test the password Azerty for id_conn.

Getting the password from the python app

Let’s start with the libraries:

import json
from cfenv import AppEnv
from jose import jwe
import requests
from requests.auth import HTTPBasicAuth

Since the app will be deployed in the same Space as the service, one can retrieve the environment variable (VCAP_SERVICES) easily with cfenv

env = AppEnv()
creds = env.get_service(name="mycreds") #name of the Cred.Store service

So creds here is a dictionary with the parameters of the service. Then, we call the API of this service to get the password associated with id_conn.  

headers = {'Accept': 'application/json',
           'Content-Type': 'application/jose',
           'DataServiceVersion': '2.0',
           'If-None-Match': '',
           'sapcp-credstore-namespace': 'test'} # name of the namespace

response = requests.get(creds.credentials.get('url')+"/password?name=id_conn",
                        headers=headers,
                        auth=HTTPBasicAuth(creds.credentials.get("username"),
                                           creds.credentials.get("password"))
                        )

The response is a JSON Web Encryption (JWE) with a four parts structure: Header, Encrypted Key, Ciphertext and the JWE Integrity Value.

The first part of the JWE will tell you something like

{
"alg": "RSA-OAEP-256",
"enc": "A256GCM",
"iat": 1631885785
}

The message encryption is implemented via symmetric encryption using Advanced Encryption Standard (AES), Galois Counter Mode (GCM) with 256 bit key size. The encryption of keys is supported using RSA Optimal Asymmetric Encryption Padding (OAEP).

The private key is given in the parameters but one needs to run some transformations before using it: first add header and footer and then convert in bytes:

private = creds.credentials.get("encryption",{}).get("client_private_key")
private_key = ("-----BEGIN PRIVATE KEY-----\n" +
               private +
               "\n-----END PRIVATE KEY-----")
private_key = private_key.encode()

Finally, one can decode the message and get the password

cred = jwe.decrypt(response.content, private_key)
cred = json.loads(cred.decode())
pwd= cred.get("value")

Another key can be useful : cred.get("modifiedAt") which indicates the last modification.

Test

If you want to “see” the password in a web application, you can encapsulate the code in this script

# -*- coding: utf-8 -*-
import os
from flask import Flask

### Add the previous lines here ####

@app.route("/")
def locale():
    return pwd

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=port)

You should get :

 

 

Conclusion

As you can see, it’s pretty easy to store and consume a password in a secured manner using this service. We have not cover the other functionalities here (like updating a password) but the main difficulties, from my point of view, are to know which python library to use for the encryption and how to format the API call. If some people are interested, I may write a follow up for the other functionalities.

That’s all for today !

Assigned Tags

      4 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Yogananda Muthaiah
      Yogananda Muthaiah

      Great Article Yann Miquel .. Keep writing many more...

      Author's profile photo Yann MIQUEL
      Yann MIQUEL

      Thanks Yogananda !

      Author's profile photo Awadhesh Kumar Gupta
      Awadhesh Kumar Gupta

      I am trying to refer below link to access username and password, part of credential store in BTP.
      https://blogs.sap.com/2021/09/19/how-to-consume-the-credential-store-with-python/

      Getting error for following :
      cred = jwe.decrypt(response.content , private_key)

      Error :
      alg: RSA-OAEP-256 is not supported by the RSA backend.

      Although, same thing working locally very fine but giving above error when trying to run on BTP space CF APP.

      Local output :

      >>> from jose import jwe
      >>> jwe.decrypt(response_content, private_key)
      b'{"id":"8XXXXXXX5","name":"dbXXXXe","modifiedAt":"2022-07-21T15:32:00.687Z","value":"ok######","status":"enabled","username":"L######","type":"password"}'>>>

      Please suggest. if i am missing to add any modules in requirement.txt or any other change.
      Thanks

      Author's profile photo Yann Miquel
      Yann Miquel
      Blog Post Author

      Hi Awadhesh Kumar Gupta,

      If only these two libraries are installed :

      python_jose==3.3.0
      requests==2.28.1

      one get the error you describe.

      To avoid the error, one must add a third library in the requirement to impose the cryptographic algo

      cryptography==38.0.1

      This third one comes with Spyder so I didn't notice the importance of this one until I look attentively to your comment.

      Why python_jose has added the RSA-OAEP-256 in the unsupported list is obscure for me yet. I will have a look.

      Did you find some information about it since you noticed the error ?

      Regards,

      Yann