I'm trying to find a python equivalent of this js function:
/**
* Generating the shared secret with the merchant private key and the ephemeral public key(part of the payment token data)
* using Elliptic Curve Diffie-Hellman (id-ecDH 1.3.132.1.12).
* As the Apple Pay certificate is issued using prime256v1 encryption, create elliptic curve key instances using the package - https://www.npmjs.com/package/ec-key
*/
sharedSecret (privatePem) {
const prv = new ECKey(privatePem, 'pem') // Create a new ECkey instance from PEM formatted string
const publicEc = new ECKey(this.ephemeralPublicKey, 'spki') // Create a new ECKey instance from a base-64 spki string
return prv.computeSecret(publicEc).toString('hex') // Compute secret using private key for provided ephemeral public key
}
public key i try to convert:
(should be a base-64 spki string?)
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYtpZKqPDqavs4KzNnMoxWdIThKe/ErKMI/l34Y9/xVkt4DU4BrCaQnGLlRGx+Pn/WHPkQg3BYoRH4xUWswNhEA==
What i manage to do:
from cryptography.hazmat.primitives.asymmetric.ec import SECP256R1, EllipticCurvePublicKey, ECDH
from cryptography.hazmat.primitives.serialization import load_pem_private_key
def __compute_shared_secret(ephemeral_public_key: str) -> bytes:
curve = SECP256R1()
key = base64.b64decode(ephemeral_public_key)
public_key = EllipticCurvePublicKey.from_encoded_point(curve, key) # problem here
server_private_key = load_pem_private_key(<private_key>, password=None)
shared_secret = server_private_key.exchange(ECDH(), public_key)
return shared_secret
ValueError: Unsupported elliptic curve point type
From what i understand i need to convert the public key to something before using it in EllipticCurvePublicKey, but i can't figure what type of conversion i should do.
According to the documentation of the JavaScript library the line
const publicEc = new ECKey(this.ephemeralPublicKey, 'spki')
imports a Base64 encoded X.509/SPKI DER key.
In Python, this can be done with load_der_public_key() of the Cryptography library as follows:
from cryptography.hazmat.primitives.serialization import load_der_public_key
import base64
...
public_key = load_der_public_key(base64.b64decode(ephemeral_public_key))
Here, ephemeral_public_key is the Base64 encoded X.509/SPKI DER key.
With this change of the Python code the shared secret can be determined.
Related
I need the Python code to extract from the p7s file the signature resulting from digitally signing a document, in both situations where the payload is inside of, and external to, the p7s file. I have tried several crypto packages (PyOpenSSL, PyCripto, Cryptography, ASN1Crypto, and a few others), to no avail. I am able to extract basically everything else (certificates, payload, signature timestamp, etc.), but not the encrypted digest (the signature).
# Python 3.10 code to extract relevant data from a PKCS#7 signature file
from datetime import datetime
from asn1crypto import cms
from cryptography import x509
from cryptography.hazmat.primitives.serialization import pkcs7
# these are the components we are going to extract
payload: bytes # the original payload
signature: bytes # the digital signature
signature_algorithm: str # the algorithm used to generate the signature
signature_timestamp: datetime # the signature's timestamp
payload_hash: bytes # the payload hash
hash_algorithm: str # the algorithm used to calculate the payload hash
cert_chain: list[x509.Certificate] # the X509 certificate chain
# define the PKCS#7 signature file path here
p7s_filepath: str = 'my_signature_file_path.p7s'
# load the p7s file
with open(p7s_filepath, 'rb') as f:
p7s_bytes: bytes = f.read()
f.close()
# extract the certificater chain
cert_chain = pkcs7.load_der_pkcs7_certificates(p7s_bytes)
# extract the needed structures
content_info: cms.ContentInfo = cms.ContentInfo.load(p7s_bytes)
signed_data: cms.SignedData = content_info['content']
signer_info: cms.SignerInfo = signed_data['signer_infos'][0]
# extract the payload (None if payload is detached)
payload = signed_data['encap_content_info']['content'].native
# extract the remaining components
signature = signer_info['signature'].native
signature_algorithm = signer_info['signature_algorithm']['algorithm'].native
hash_algoritmo = signer_info['digest_algorithm']['algorithm'].native
signed_attrs = signer_info['signed_attrs']
for signed_attr in signed_attrs:
match signed_attr['type'].native:
case 'message_digest':
payload_hash = signed_attr['values'][0].native
case 'signing_time':
signature_timestamp = signed_attr['values'][0].native
This question is the inverse of the existing one here:
Encrypt in python 3.7 and decode in NODEJS 12 .
I would prefer to use the exact equivalent of tweet-nacl on python but that project says it is old and not recommended https://github.com/warner/python-tweetnacl . Their recommended replacement is https://github.com/pyca/pynacl : but that one is an interface to libsodium not tweet-nacl and there is no clear documentation on how to achieve the decryption.
Here is the JS encryption:
let msgArr = naclutil.decodeUTF8(jprint(msg))
let nonce = nacl.randomBytes(nacl.box.nonceLength)
let keyPair = this.genKeyPair()
let encrypted = nacl.box(
msgArr,
nonce,
naclutil.decodeBase64(pubKey),
naclutil.decodeBase64(keyPair.privkey)
)
let nonce64 = naclutil.encodeBase64(nonce)
let encrypted64 = naclutil.encodeBase64(encrypted)
The (working) tweet-nacl javascript decryption code is:
const decryptedMessage = nacl.box.open(
naclutil.decodeBase64(payload.encrypted.encrypted),
naclutil.decodeBase64(payload.encrypted.nonce),
naclutil.decodeBase64(payload.encrypted.ephemPubKey),
naclutil.decodeBase64(privKey)
)
const decodedMessage = naclutil.encodeUTF8(decryptedMessage)
My problem is that for pynacl they do not show any examples of using the ephemPubKey for decryption. The examples I could find were like the following:
import binascii
from nacl.encoding import HexEncoder
from nacl.exceptions import CryptoError
from nacl.secret import Aead, SecretBox
benc= binascii.unhexlify(encrypted)
bnonce = binascii.unhexlify(nonce)
box = SecretBox(privKey, encoder=HexEncoder)
decrypted = box.decrypt(benc, bnonce, encoder=HexEncoder),
Has anyone been able to get the tweet-nacl Javascript generated encryption successfully decrypted into python?
SecretBox and thus the PyNaCl example you posted is for symmetric encryption, s. here. You can find the documentation for public key encryption with PyNaCl here and for PyNaCl in general here.
In the following example, a plaintext is encrypted with TweetNaCl.js and decrypted with PyNaCl.
JavaScript side - Encryption with TweetNaCl.js:
var secretKey_js = nacl.util.decodeBase64("FJGsHP0dMkDNkpAkT4hZrcbv27L8XNO8ymhLxpPpDkE=");
var publicKey_py = nacl.util.decodeBase64("0EyrzGW6qn0EGEV0Cx2Z7tQeln6FdwZVINz0FezlvTM=");
var nonce = nacl.randomBytes(24)
var msgStr = "The quick brown fox jumps over the lazy dog";
var message = nacl.util.decodeUTF8(msgStr);
var box_js = nacl.box(message, nonce, publicKey_py, secretKey_js)
console.log(nacl.util.encodeBase64(nonce)) // 2e8WuEr0+5nc14VBxQrOl4ob6guOTySr
console.log(nacl.util.encodeBase64(box_js)) // eJ8sO0mFNaaWLeXVcNNpw0PurwfINp/BlnErSzOnxXJ5zqu3wLrW4fHIa4kIAxFkuMVJaf0AR4pYon0=
The code is basically the same as your code with the difference that the keys are not generated but imported.
Python side - Decryption with PyNaCl:
import base64
from nacl.public import PrivateKey, PublicKey, Box
from nacl.encoding import Base64Encoder
secretKeyB64_py = "XVdFnozXd+7xm6MVazPemgSq6un+fGpDvwgxo9UbsdM=";
publicKeyB64_js = "ixxgLis2RzqMWys76HuoH7TwrwBbXoDrwl3jGsRysRI=";
secretKey_py = PrivateKey(secretKeyB64_py, encoder=Base64Encoder)
publicKey_js = PublicKey(publicKeyB64_js, encoder=Base64Encoder)
nonce = base64.b64decode("2e8WuEr0+5nc14VBxQrOl4ob6guOTySr");
box_js = base64.b64decode("eJ8sO0mFNaaWLeXVcNNpw0PurwfINp/BlnErSzOnxXJ5zqu3wLrW4fHIa4kIAxFkuMVJaf0AR4pYon0=");
box_py = Box(secretKey_py, publicKey_js)
data = box_py.decrypt(nonce + box_js)
print(data) # b'The quick brown fox jumps over the lazy dog'
In the example above, the keys have been imported. If a key pair needs to be generated, this is done with PyNaCl as follows:
from nacl.public import PrivateKey
secretKeyNew = PrivateKey.generate()
publicKeyNew = secretKeyNew.public_key
About compatibility:
TweetNaCl.js and PyNaCl are compatible. As you described, although PyNaCl is a wrapper of Libsodium for Python (s. here and here), but Libsodium itself is a port of NaCl (s. here), as is TweetNacl.js for JavaScript (here).
So ultimately, both TweetNaCl and PyNaCl are based on NaCl, the original library implemented by Bernstein et al., s. here, and are therefore compatible (except perhaps for a few minor syntactical differences).
I have been trying for a few days to validate some message signed with a private key in python. Note that the message has been signed using Ruby.
When I sign the same message in python I can verify it no problem. Note that I have already validated that the hash are the same.
Python code:
string_to_encrypt = b"aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
sha1 = SHA.new()
sha1.update(string_to_encrypt)
# load private key
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, open('./license.pem', 'rb').read())
sign_ssl = OpenSSL.crypto.sign(pkey, sha1.digest(), 'RSA-SHA1')
b64_ssl = base64.b64encode(sign_ssl)
Ruby:
string_to_encrypt = "aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
sha1 = Digest::SHA1.digest(string_to_encrypt)
#sign it
private_key_file = File.join(File.dirname(__FILE__), 'license.pem')
rsa = OpenSSL::PKey::RSA.new(File.read(private_key_file))
signed_key = rsa.private_encrypt(sha1)
#update the license string with it
x = Base64.strict_encode64(signed_key)
I would expect b64_ssl and x to contain the same value and they don't. Could someone explain to me what I missing there?
Neither of these code snippets is actually producing the correct signature.
In the Ruby OpenSSL library you want to be using the sign method, not the private_encrypt method, which is a low level operation that doesn’t do everything required to produce a valid signature.
In both libraries the sign operation performs the hashing for you, you don’t need to do this beforehand. In fact your Python code is actually hashing the data twice.
Try the following Python code:
import OpenSSL
import base64
string_to_encrypt = b"aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
# load private key
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, open('./license.pem', 'rb').read())
sign_ssl = OpenSSL.crypto.sign(pkey, string_to_encrypt, 'SHA1')
b64_ssl = base64.b64encode(sign_ssl)
print(b64_ssl.decode())
which produces the same output as this Ruby code:
require 'openssl'
require 'base64'
string_to_encrypt = "aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
#sign it
private_key_file = File.join(File.dirname(__FILE__), 'license.pem')
rsa = OpenSSL::PKey::RSA.new(File.read(private_key_file))
signed_key = rsa.sign('sha1', string_to_encrypt)
#update the license string with it
x = Base64.strict_encode64(signed_key)
puts x
I'm trying to understand the steps to take an OpenSSH public key like so:
ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAqmEmDTNBC6O8HGCdu0MZ9zLCivDsYSttrrmlq87/YsEBpvwUTiF3UEQuFLaq5Gm+dtgxJewg/UwsZrDFxzpQhCHB6VmqrbKN2hEIkk/HJvCnAmR1ehXv8n2BWw3Jlw7Z+VgWwXAH50f2HWYqTaE4qP4Dxc4RlElxgNmlDPGXw/dYBvChYBG/RvIiTz1L+pYzPD4JR54IMmTOwjcGIJl7nk1VjKvl3D8Wgp6qejv4MfZ7Htdc99SUKcKWAeHYsjPXosSk3GlwKiS/sZi51Yca394GE7T4hZu6HTaXeZoD8+IZ7AijYn89H7EPjuu0iCAa/cjVzBsFHGszQYG+U5KfIw==
And then to convert it into an standard fingerprint like so:
2048 49:d3:cb:f6:00:d2:93:43:a6:27:07:ca:12:fd:5d:98 id_rsa.pub (RSA)
I have attempted to dive into the OpenSSH source to understand this, but it is over my head. My first guess was to do a simple MD5 on the key text, but the result does not match the above output.
It is the MD5 sum of the base64-encoded key:
import base64
import hashlib
def lineToFingerprint(line):
key = base64.b64decode(line.strip().split()[1].encode('ascii'))
fp_plain = hashlib.md5(key).hexdigest()
return ':'.join(a+b for a,b in zip(fp_plain[::2], fp_plain[1::2]))
https://github.com/ojarva/sshpubkeys
pip install sshpubkeys
Usage:
import sshpubkeys
key = sshpubkeys.SSHKey("ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAqmEmDTNBC6O8H" +
"GCdu0MZ9zLCivDsYSttrrmlq87/YsEBpvwUTiF3UEQuFLaq5Gm+dtgxJewg/UwsZrDFxz" +
"pQhCHB6VmqrbKN2hEIkk/HJvCnAmR1ehXv8n2BWw3Jlw7Z+VgWwXAH50f2HWYqTaE4qP4" +
"Dxc4RlElxgNmlDPGXw/dYBvChYBG/RvIiTz1L+pYzPD4JR54IMmTOwjcGIJl7nk1VjKvl" +
"3D8Wgp6qejv4MfZ7Htdc99SUKcKWAeHYsjPXosSk3GlwKiS/sZi51Yca394GE7T4hZu6H" +
"TaXeZoD8+IZ7AijYn89H7EPjuu0iCAa/cjVzBsFHGszQYG+U5KfIw== user#host")
print(key.bits) # 2048
print(key.hash()) # '49:d3:cb:f6:00:d2:93:43:a6:27:07:ca:12:fd:5d:98'
In python I am trying to import a public key as follows (omitting a lot of characters):
public = "MIGfMA0G...."
RSA.importKey(public)
but I get the error
ValueError: RSA key format is not supported
How to create a RSA object with the public key when I have the key in a string?
In order to import the key, if it has the header, is imported correctly:
from Crypto.PublicKey import RSA
key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCS{...}QVAwwIDAQAB\n-----END PUBLIC KEY-----"
RSA.importKey(key)
So the only thing you need is to place the beginning and the end.