2

I am trying to use the Google API with a oAuth service account, with Python 3.4. One of the steps is to generate a JSON Web Token, for which I use PyJWT.

My code for the generation is the following:

# opening the certificate downloaded from the Google API console
# it is password protected by the standard password ('notasecret')
p12 = OpenSSL.crypto.load_pkcs12(open('certfromgoogle.p12', 'rb').read(), 'notasecret')

# extracting the private key from the certificate and dumping it to a PEM
# format (FILETYPE_PEM)
private_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey())

# at that stage, private_key contains the private key as
# b'-----BEGIN PRIVATE KEY-----\nMIICdg(...)FIyw==\n-----END PRIVATE KEY-----\n'

# trying to get the JWT
encoded = jwt.encode(claim, private_key, algorithm='RS256', headers={"alg": "RS256", "typ": "JWT"})

The call to jwt.encode crashes with TypeError: Expecting a PEM-formatted key. The full traceback:

Traceback (most recent call last):
  File "C:/Users/w_000/PycharmProjects/syncmagazines/testcrypto.py", line 20, in <module>
    encoded = jwt.encode(claim, private_key, algorithm='RS256', headers={"alg": "RS256", "typ": "JWT"})
  File "C:\Python34\lib\site-packages\jwt\api.py", line 118, in encode
    key = alg_obj.prepare_key(key)
  File "C:\Python34\lib\site-packages\jwt\algorithms.py", line 170, in prepare_key
    raise TypeError('Expecting a PEM-formatted key.')
TypeError: Expecting a PEM-formatted key.

The private key, however, seems to be extracted correctly.

Why isn't this format correct?

WoJ
  • 27,165
  • 48
  • 180
  • 345

2 Answers2

1

Having examined the PyJWT source code, it is apparent that the library expects the PEM data to be a string type, but you are providing a bytestring (evident in your question by the b'...' literal). The offending function is prepare_key, along with the definition of acceptable string types.

You must decode the private key data into the native str type:

private_key_bytes = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey())
private_key = private_key_bytes.decode('utf-8')

This only seems to be required for Python 3, but the above code should work for Python 2 as well.

frasertweedale
  • 5,424
  • 3
  • 26
  • 38
  • Thanks, this was exactly the problem. After adding `.decode('utf-8')` to the `private_key=...` line I got a correct JWT. – WoJ May 10 '15 at 15:33
0

What is special for a private key to be PEM-formatted?

PEM is a presentation encoding. It has the familiar -----BEGIN XXX----- and -----END XXX-----.

I think BEGIN PRIVATE KEY is a PKCS #8 private key. Perhaps the library wants a PKCS #1 private key with BEGIN RSA PRIVATE KEY. BEGIN RSA PRIVATE KEY is also known as a Traditional key encoding (as opposed to PKCS #8).

You should check the relevant documentation and provide the key in the correct format.

To convert from a traditional key to a PKCS #8 key, see the OpenSSL man pages for pkcs(1). -topk8 is of interest. Also see How to convert PKCS#8-formatted PEM private key to the tranditional format?

To convert from a PKCS #8 key to a a traditional key, see the OpenSSL man pages for rsa(1). Also see Convert PEM traditional private key to PKCS8 private key?

Community
  • 1
  • 1
jww
  • 97,681
  • 90
  • 411
  • 885
  • The error states that the required format is PEM, which is what I dump (code commented for clarity) – WoJ May 09 '15 at 11:12
  • @WoJ - You provided a PKCS #8 PEM encoded key. Does the library want a PKCS #1 PEM encoded key? PKCS #1 PEM is considered a *Traditional* key in OpenSSL. – jww May 09 '15 at 22:24