Search code examples
pythonrsapycryptoipfspycryptodome

How to decode IPFS private and public key in DER/PEM format?


how to decode IPFS private and public key in DER/PEM format that can work with the pycryptodome library(for Python 3)? I get the keys from the IPFS configuration file as strings, so I will not explain this process here.

What I'm trying to do:

import base64, Crypto

publicKey = "CAASpgIwgE ... jkupAgMBAAE="
privateKey = "CAASqQkwgg ... Xmzva/Km7A=="

publicKey = base64.b64decode(publicKey)
key = Crypto.PublicKey.RSA.import_key(publicKey)
crypter = Crypto.Cipher.PKCS1_OAEPPKCS1_OAEP.new(key)
encryptedData = crypter.encrypt(data.encode())
result = base64.b64encode(encryptedData).decode()

I get the following exception:

key = Crypto.PublicKey.RSA.importKey(publicKey)
  File "/usr/local/lib/python3.6/site-packages/Crypto/PublicKey/RSA.py", line 754, in import_key
    raise ValueError("RSA key format is not supported")

Similar problem with privateKey. In what format is the key and how to convert it to an acceptable format?

import_key function source code is there: https://github.com/Legrandin/pycryptodome/blob/master/lib/Crypto/PublicKey/RSA.py#L682


Solution

  • The solution was not as straightforward as it seemed to me at first glance.

    For a start, you need to understand that the contents of the PrivateKey and PublicKey variables are not just a pure key encoded in Base64, it is a protobuf object serialized in ByteArray and then encoded in Base64. In order to get a key out of it, you first need to get a schema of this object, which is available by reference.

    We save this file and follow the instructions on this page. In short, I ran the command protoc --python_out=. crypto.proto to create a Python module called crypto_pb2.py.

    All preparations are complete and now go to the code:

    import crypto_pb2
    import base64
    
    publicKey = "CAASpgIwgE ... jkupAgMBAAE="
    privateKey = "CAASqQkwgg ... Xmzva/Km7A=="
    

    You must first decode the base64 string to a byte array:

    decoded = base64.b64decode(publicKey) 
    

    This function deserializes an array of bytes into a familiar Python protobuf object, I took it from this answer and modified a little:

    def deserialize(byte_message, proto_type):
        module_, class_ = proto_type.rsplit('.', 1)
        class_ = getattr(crypto_pb2, class_) # crypto_pb2 is a name of module we recently created and imported
        rv = class_()
        rv.ParseFromString(byte_message) # use .SerializeToString() to reverse operation
        return rv
    

    Further we call the function, pass the decoded base64 and the name of the class to which it corresponds (PublicKey for publicKey and PrivateKey for privateKey), I'm interested in the Data property.

    publicKey = deserialize(decoded, 'crypto.pb.PublicKey').Data
    

    Now you can transfer it to the import_key function as ByteArray. Do not perform additional conversions.

    key = Crypto.PublicKey.RSA.import_key(publicKey)
    crypter = Crypto.Cipher.PKCS1_OAEPPKCS1_OAEP.new(key)
    encryptedData = crypter.encrypt(data.encode())
    result = base64.b64encode(encryptedData).decode()