Home > front end >  Curve 25519 Symmetric Key with Python
Curve 25519 Symmetric Key with Python

Time:04-16

I'm using Apple's CryptoKit to create keys for an iOS app, encrypt the data and then send it to the backend via JSON and store it in a PGSQL database.

While all of that is working perfectly, I need to be able to decrypt the data from the backend, and thus need to be able to create the same symmetric key I used to encrypt the data.

When I created the keys via Swift, it was done as follows:

let privateKey = Curve25519.KeyAgreement.PrivateKey()
let publicKey = privateKey.publicKey
let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: publicKey)
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(using: SHA512.self,
    salt: "\(vhvioerhvoreovjreoivgifjtughrygryrufejewdf))".data(using: .utf8)!,
    sharedInfo: Data(),
    outputByteCount: 32)

Note: The salt is just me typing a bunch of random characters for this example code, but you get the idea.

I need to accomplish the same thing using Python. The keys are base64 encoded strings sent via JSON to the backend as well so I need to do a b64decode on them (which I've already got working).

I tried using pynacl but I cannot figure out how to create the symmetric key, using sha512 and the same salt I used to create the symmetric key in Swift. I am also in no way tied to pynacl if there's a better option.

Again, the above code is just an example of the process used to create a private, public, and symmetric key in Swift. I am actually using a public key from one account, with a private key from another to create the symmetric key and encrypt the data. I will be doing the same in reverse to decrypt (i.e. a public key from the private key account, and a private key from the public key account).

So far, playing around with it in python I have the following (but again, I am not tied to pynacl if there's a better solution):

from nacl.public import Box, PrivateKey, PublicKey, SealedBox
from nacl.hash import sha512
import nacl.encoding
from base64 import b64decode, b64encode
import binascii

privKeyRead = b64decode('uBruInrnbtrberverv6XZZqQDDeS4SwORSHriW04=')
private_key = PrivateKey(privKeyRead)

pubKeyRead = b64decode('UIZSc3QBfewojfoewjgowjgCqA/P8PjXDlQwU7rTHFBw=')
public_key = PublicKey(pubKeyRead)

salt = b64decode('HIHIUGUBLJOIHIBIOHO9nM0NSSkNDejUwcXJMNUdlUT0=')

cryptoBox = Box(private_key, public_key)
sharedSecret = Box.shared_key(cryptoBox)

# Print Keys and Salt
print("Private Key:", privKeyRead)
print("Public Key:", pubKeyRead)
print("Salt:", salt)
print("Crypto Box:", cryptoBox)
print("Shared Secret:", sharedSecret)

return printed

Note: The privKeyRead, pubKeyRead, and salt values have garbage characters for this example as I can't obviously post the real values. I have also noticed that both the Crypto Box and the Shared Secret are identical, so I'm pretty sure I would only need one and not both.

Lastly, and I don't want anyone to get hung up on this, I am using Zope5 which is why you see my python example as it is. This is a python script in zope which is perfectly valid. I can also create external methods, so if you have functions, etc. that will work better, please feel free to post exactly how you would do it where zope isn't in the equation. I'll reconfigure for zope if necessary.

CodePudding user response:

The Swift code generates a private key, determines the related public key, derives a shared secret using X25519, and derives the symmetric key using HKDF:

import Foundation
import Crypto

let privateKey = Curve25519.KeyAgreement.PrivateKey()
let publicKey = privateKey.publicKey
let sharedSecret = try! privateKey.sharedSecretFromKeyAgreement(with: publicKey)
let symmetricKey = sharedSecret.hkdfDerivedSymmetricKey(using: SHA512.self,
    salt: "a test salt".data(using: .utf8)!,
    sharedInfo: Data(),
    outputByteCount: 32)
print("Private key  : ", privateKey.rawRepresentation.base64EncodedString())
print("Public key   : ", publicKey.rawRepresentation.base64EncodedString())
print("Shared secret: ", sharedSecret.withUnsafeBytes {return Data(Array($0)).base64EncodedString()})
print("Symmetric key: ", symmetricKey.withUnsafeBytes {return Data(Array($0)).base64EncodedString()})

A possible output is:

Private key  :  8AtqpW6UJBhAEzTnvHMQ8ki28TrDvAEbKuV3FDiROWw=
Public key   :  kICzRWQYcawmlVJpSJ2TINuUDmI0xGm2BnH10qHVxxs=
Shared secret:  ijACBaZfaCQuvwwILb5uncYroZ4MLBzOfNZLD5khjz4=
Symmetric key:  Il570rzDZ9brWBtxUtWv/Hv29rN6pBls7/AtOK 2oPU=

For the implementation in Python a library is needed that supports X25519 and HKDF, e.g. Cryptography (Cryptography seems to be the better choice here compared to PyNaCl, since the latter does not support HKDF):

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
import base64

# X25519
private_key = X25519PrivateKey.generate()
public_key = private_key.public_key()
shared_secret = private_key.exchange(public_key)

# HKDF
symmetric_key = HKDF(
    algorithm=hashes.SHA512(),
    length=32,
    salt='a test salt'.encode('utf-8'),
    info=b'',
).derive(shared_secret)

print(base64.b64encode(shared_secret))
print(base64.b64encode(symmetric_key))

Test:
For testing, use the keys of the Swift code, which can be imported as follows:

from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
...
private_key = X25519PrivateKey.from_private_bytes(base64.b64decode("8AtqpW6UJBhAEzTnvHMQ8ki28TrDvAEbKuV3FDiROWw="))
public_key = X25519PublicKey.from_public_bytes(base64.b64decode("kICzRWQYcawmlVJpSJ2TINuUDmI0xGm2BnH10qHVxxs="))

With this, the Python code provides the shared secret and the key of the Swift code.

  • Related