Generating JWS token in python (Example in C) - python

Hi I have this code from this article
https://medium.com/#alexastrum/firebase-auth-for-iot-devices-286679a8b59e
I was wondering what the equivalent python code would be thanks.
String getDeviceToken()
{
String header = "";
DynamicJsonDocument json(1024);
// ATECCx08 crypto chips only support ES256:
// https://github.com/MicrochipTech/cryptoauthlib/blob/master/lib/jwt/atca_jwt.c
json["alg"] = "ES256";
json["kid"] = DEVICE_ID;
serializeJson(json, header);
String payload;
json.clear();
json["nonce"] = NONCE;
serializeJson(json, payload);
return ECCX08JWS.sign(/* slot */ 0, header, payload);
}

import jwt as jwt
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import ec
# Generates the private key
private_key = ec.generate_private_key(ec.SECP256R1(), default_backend())
# Generates the public key
public_key = private_key.public_key()
jwt.encode(
payload={"nonce": NONCE},
key=private_key,
algorithm="ES256",
headers={"alg": "ES256", "kid": DEVICE_ID},
)

Related

RSA PSS signature verification between python (cryptography) and .NET5 (System.Security.Cryptography) fails

I need to take the RSA PSS signatures of a message generated from Python and validate in .NET. But validation in .NET fails.
I have generated the RSA key pair using the following command:
openssl req -x509 -nodes -newkey rsa:4096 -keyout /tmp/certs/private.pem -out /tmp/certs/public.pem -days 365
Python code:
from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding
from nacl.encoding import HexEncoder
def sign(message: str) -> str:
with open("/tmp/certs/private.pem", "rb") as pem:
private_key = serialization.load_pem_private_key(
pem.read(),
password=None,
backend=default_backend(),
)
return HexEncoder.encode(
private_key.sign(
message.encode("utf-8"),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256(),
)
).decode("utf-8")
def verify(message: str, signature: str) -> bool:
with open("/tmp/certs/public.pem", "rb") as pem:
cert = x509.load_pem_x509_certificate(pem.read(), default_backend())
public_key = cert.public_key()
try:
signature = HexEncoder().decode(signature)
public_key.verify(
signature,
message.encode(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256(),
)
return True
except InvalidSignature:
return False
def main():
message = "hello"
signature = sign(message)
print(f"Signature: {signature}")
# print(f"isValidated: {verify(message, signature)}")
if __name__ == '__main__':
main()
C# code targeting .NET 5.0:
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace HelloWorld
{
internal static class Program
{
private static string ByteArrayToString(IReadOnlyCollection<byte> ba)
{
var hex = new StringBuilder(ba.Count * 2);
foreach (var b in ba)
hex.AppendFormat("{0:x2}", b);
return hex.ToString();
}
private static byte[] StringToByteArray(string hex)
{
var numberChars = hex.Length;
var bytes = new byte[numberChars / 2];
for (var i = 0; i < numberChars; i += 2)
bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
return bytes;
}
private static string Sign(string message)
{
var rsa = RSA.Create();
rsa.ImportFromPem(File.ReadAllText("/tmp/certs/private.pem"));
var messageBytes = Encoding.UTF8.GetBytes(message);
var signature = rsa.SignData(messageBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
return ByteArrayToString(signature);
}
private static bool Verify(string message, string signature)
{
var rsa = RSA.Create();
var cert = X509Certificate.CreateFromCertFile("/tmp/certs/public.pem");
var publicKey = cert.GetPublicKey();
rsa.ImportRSAPublicKey(publicKey, out _);
var messageBytes = Encoding.UTF8.GetBytes(message);
var signatureBytes = StringToByteArray(signature);
return rsa.VerifyData(messageBytes, signatureBytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
public static void Main()
{
const string message = "hello";
// var signature = Sign(message);
const string signature = <value_from_python>;
// Console.WriteLine($"Signature is: {signature}");
Console.WriteLine($"isValidated: {Verify(message, signature)}");
}
}
}
The verification fails because both codes use different salt lengths. The Python code explicitly applies the maximum salt length, the C# code defaults to the digest output length. The latter is also defined as PSS default in RFC8017, A.2.3. RSASSA-PSS.
So to fix this
either use the digest output length in the Python code, i.e. 32 (bytes for SHA256),
or in the C# code the maximum salt length: signature length - digest output length - 2 = 512 - 32 - 2 = 478 (for a 4096 bits key).
As far as I know this is not possible with .NET onboard means, but with BouncyCastle and the class Org.BouncyCastle.Crypto.Signers.PssSigner, which also provides constructors for defining the salt length. You can find an example here, last section.

Generate a public key with a predefined modulus and exponent

"n": "rKZ-1zdz_CoLekSynOtyWv6cPSSkV28Kb9kZZHyYL-yhkKnH_bHl8OpWiGxQiKP0ulLRIaq1IhSMetkZ8FfXH-iptIDu4lPb8gt0HQYkjcy3HoaKRXBw2F8fJQO4jQ-ufR4l-E0HRqwLywzdtAImNWmju3A4kx8s0iSGHGSHyE4EUdh5WKt-NMtfUPfB5v9_2bC-w6wH7zAEsI5nscMXnvz1u8w7g2_agyhKSK0D9OkJ02w3I4xLMlrtKEv2naoBGerWckKcQ1kBYUh6WASPdvTqX4pcAJi7Tg6jwQXIP1aEq0JU8C0zE3d33kaMoCN3SenIxpRczRzUHpbZ-gk5PQ",
"e": "AQAB",
How can I generate a public key from these values? Preferable via python or Linux programs. I'm sorry if the question is nonsense because of invalid values.
The source is here.
In Python, you can use Python-JOSE
What you got there in your link is a JSON Web Key Set (JWKS), a JSON formated array of JSON Web Keys (JWK).
n and e are the modulus and exponent of a RSA public key.
The function jwk.construct can directly create a key from a JWK. The key can then be used e.g. to verify a JWT (key.verify).
You can also use jwt.decode like shown in the code below and pass the JWK directly, or in PEM format.
from jose import jwk
from jose.utils import base64url_decode
token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IlctNjduZWt0WVRjOEpWWVBlV0g1c1dlN1JZVm5uMFN5NzQxZjhUT0pfQWMifQ.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.hiKxeC66LIyVKOXjiOk7iScFPy_5-ATw7hEfqGij8sBZmwXAeTPT5BRFYHitFKSXomGqmy_63LLvg4zbhcTTmNf8XIeDAuLsC32soO5woSByisswWHVf8BgxMkI_FPW_oEtEQ8Xv3FL_1rF9j9Oy3jIjgjqhFhXUtsSQWAeuGYH-OQljFwiuO5Bqexcw-H71OEWvQLQof_6KJ0viJyte8QEwEVridyO834-ppHzeaoW2sTvZ22ZNfxPCew0Ul2V_TxHTtO7ZuJCZ81EmeIV6dYJ2GrYh3UN1x1PHy4-tEn-PL4otlaO3PYOcXfCHxHa6xtPsquzPZJnB1Vq8zULLfQ"
rsa_key = {
"kty": "RSA",
"kid": "W-67nektYTc8JVYPeWH5sWe7RYVnn0Sy741f8TOJ_Ac",
"use": "sig",
"alg": "RS256",
"n": "kFpGoVmBmmKepvBQiwq3hU9lIAuGsAPda4AVk712d3Z_QoS-5veGp4yltnyEFYyX867GOKDpbH7OF2uIjDg4-FPZwbuhiMscbkZzh25SQmfRtCT5ocUloQiopBcNAE-sd1p-ayUJWjhPrFoBrBLZHYxVEjY4JrWevQDj7kSeX7eJpud_VuZ77TNoIzj7d_iUuJUUlqF1ZF540igHKoVJJ6ujQLHh4ob8_izUuxX2iDq4h0VN3-uer59GsWw6OHgkOt85TsjMwYbeN9iw_7cNfLEYpSiH-sVHBCyKYQw7f8bKaChLxDRhUUTIEUUjGT9Ub_A3gOXq9TIi8BmbzrzVKQ",
"e": "AQAB"
}
key = jwk.construct(rsa_key)
message, encoded_sig = token.rsplit('.', 1)
decoded_sig = base64url_decode(encoded_sig + '=' * (4 - len(encoded_sig) % 4)) # looks weird, but without added padding I got errors
res = key.verify(bytes(message, "UTF-8"), decoded_sig)
# jwt.decode(token=token, key=key.to_pem().decode(), algorithms= 'RS256') # with PEM key
payload = jwt.decode(token=token, rsa_key, algorithms= 'RS256') # with JWK
print(res)
print(payload)
The result will be:
True
{'sub': '1234567890', 'name': 'John Doe', 'admin': True, 'iat': 1516239022}
which means the token could be verified with that key.

Create Signature using SHA256 encrypyion with .p12 file in python

Can any one help to do the same code in Python
<?php
$payload = '{"amount":205.16,"currency":"AED","language":"en","merchant_code":"O8Ryrfvy8aUb","merchant_order":"931","redirect_urls":{"cancel":"http://127.0.0.1:8000/api/pointspay-cancel","fail":"http://127.0.0.1:8000/api/pointspay-failed","success":"http://127.0.0.1:8000/api/pointspay-success"},"timestamp":1604722764987,"type":"direct"}';
echo x509_fingerprint($payload, '/home/tmt/Documents/Protect4less/LoyLogic/pointspay_certificate.p12', 'Protect4Less#123', 'sha256') . "\n\n";
function x509_fingerprint($payload, $cert_path, $cert_pass, $algorithm = 'sha256')
{
$algorithm = in_array($algorithm, array(
'sha1',
'md5',
'sha256'
)) ? $algorithm : 'sha1';
// 1. Capture the private key from certificate
if (!$cert_store = file_get_contents($cert_path)) {
echo "Error: Unable to read the cert file\n";
exit;
}
if (openssl_pkcs12_read($cert_store, $cert_info, $cert_pass)) {
} else {
echo "Error: Unable to read the cert store.\n";
exit;
}
$privateKey = $cert_info['pkey'];
print_r($privateKey);
// 2. create message digest
$messageDigest = openssl_digest($payload, $algorithm, true);
// 3. Sign the message digest using private key
openssl_private_encrypt($messageDigest, $encryptedData, $privateKey);
return base64_encode($encryptedData);
}
?>
This is PHP code and I want to do the same steps in PYTHON, can any 1 help me to do so.
I tried many things in python but was not able to do so.
Following is the Python code I tried, but the final signature doesn't match with the PHP signature
from OpenSSL import crypto
import hashlib
from base64 import b64encode
str1 = b'{"amount":205.16,"currency":"AED","language":"en","merchant_code":"O8Ryrfvy8aUb","merchant_order":"931","redirect_urls":{"cancel":"http://127.0.0.1:8000/api/pointspay-cancel","fail":"http://127.0.0.1:8000/api/pointspay-failed","success":"http://127.0.0.1:8000/api/pointspay-success"},"timestamp":1604722764987,"type":"direct"}'
psw = 'Protect4Less#123'
p12 = crypto.load_pkcs12(open("/home/tmt/Documents/Protect4less/LoyLogic/pointspay_certificate.p12", 'rb').read(), psw)
pkey = p12.get_privatekey()
my_hash2 = hashlib.sha256() #Choose SHA256 and update with same bytes
my_hash2.update(str1)
hash_digest = my_hash2.hexdigest()
print("Result after digesting: " + str(my_hash2.hexdigest()))
# data = str1
my_signature = crypto.sign(pkey, str1, 'sha256')
signature_b64 = b64encode(my_signature)
print(signature_b64)

Why can't I sign these claims with my JWK?

I am writing this python code to create RSA private and public keys. Then create a JWK from the private key, then sign the claims with that JWK.
#!/usr/bin/env python
import time
from jose import jwk
from jose import jws
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend
key = rsa.generate_private_key(backend=crypto_default_backend(), public_exponent=65537, key_size=2048)
private_key = key.private_bytes(crypto_serialization.Encoding.PEM, crypto_serialization.PrivateFormat.PKCS8, crypto_serialization.NoEncryption())
public_key = key.public_key().public_bytes(crypto_serialization.Encoding.OpenSSH, crypto_serialization.PublicFormat.OpenSSH)
key = jwk.construct(private_key, 'RS256')
print 'key.to_dict() = {}'.format(key.to_dict())
claims = {
'iss': 'https://e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com',
'sub': 'fdfdc610f849726e',
'aud': '20c875ad0d4bfc94',
'iat': time.time() - 20,
'exp': time.time() + 20,
'jti': '807443d3-3b27-4bf9-8e3e-e3f90e1ea055',
'typ': 'id_token'
}
print 'About to sign'
signed = jws.sign(claims, key, algorithm='RS256')
When I run it, it fails at the signing step:
key.to_dict() = {
'e': 'AQAB',
'kty': 'RSA',
'alg': 'RS256',
'n': 'uJ1_BLAH_BLAH_BLAH_veQ',
'q': '8Sa_BLAH_BLAH_BLAH_-Hs',
'p': 'w_t_BLAH_BLAH_BLAH_p5s',
'qi': 'OGz_BLAH_BLAH_BLAH_91U',
'dq': 'D2n_BLAH_BLAH_BLAH_5FM',
'dp': 'pDi_BLAH_BLAH_BLAH_J2k',
'd': 'oV0_BLAH_BLAH_BLAH_VLQ'
}
About to sign
Traceback (most recent call last):
File "./my_file.py", line 56, in <module>
signed = jws.sign(claims, key, algorithm='RS256')
File "my-virtual-env/lib/python2.7/site-packages/jose/jws.py", line 47, in sign
signed_output = _sign_header_and_claims(encoded_header, encoded_payload, algorithm, key)
File "my-virtual-env/lib/python2.7/site-packages/jose/jws.py", line 168, in _sign_header_and_claims
raise JWSError(e)
jose.exceptions.JWSError: Unable to parse an RSA_JWK from key: <jose.backends.pycrypto_backend.RSAKey object at 0x101761190>
What is causing this error and how can I make this work??
Using RS256, you need to give the private key to jws.sign(), but not with the JWK format.
Therefore, just replace
signed = jws.sign(claims, key, algorithm='RS256')
by
signed = jws.sign(claims, private_key, algorithm='RS256')
And it will work correctly.
Finally, the whole source is:
#!/usr/bin/env python
import time
from jose import jws
from cryptography.hazmat.primitives import serialization as crypto_serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.backends import default_backend as crypto_default_backend
key = rsa.generate_private_key(backend=crypto_default_backend(), public_exponent=65537, key_size=2048)
private_key = key.private_bytes(crypto_serialization.Encoding.PEM, crypto_serialization.PrivateFormat.PKCS8, crypto_serialization.NoEncryption())
claims = {
'iss': 'https://e97b8a9d672e4ce4845ec6947cd66ef6-sb.baas.nintendo.com',
'sub': 'fdfdc610f849726e',
'aud': '20c875ad0d4bfc94',
'iat': time.time() - 20,
'exp': time.time() + 20,
'jti': '807443d3-3b27-4bf9-8e3e-e3f90e1ea055',
'typ': 'id_token'
}
print 'About to sign'
signed = jws.sign(claims, private_key, algorithm='RS256')
print signed
The result is something like:
About to sign
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmZGZkYzYxMGY4NDk3MjZlIiwiaXNzIjoiaHR0cHM6Ly9lOTdiOGE5ZDY3MmU0Y2U0ODQ1ZWM2OTQ3Y2Q2NmVmNi1zYi5iYWFzLm5pbnRlbmRvLmNvbSIsImp0aSI6IjgwNzQ0M2QzLTNiMjctNGJmOS04ZTNlLWUzZjkwZTFlYTA1NSIsImV4cCI6MTU0NTkzNTQ1Mi4wMzAxMTUsImlhdCI6MTU0NTkzNTQxMi4wMzAxMTMsInR5cCI6ImlkX3Rva2VuIiwiYXVkIjoiMjBjODc1YWQwZDRiZmM5NCJ9.Qfmi607XTtq8bc4daJ-GxoYy1B761nRahWSok9ga0CB8dqbDXN0FhcM38dNj0LMFoi98hxB9o0EwY1mpBEMA3rNK9C6Bbg1hKhkYE5lRFXH5_lLk2tE6YrbK7p71S54hkTfEZgPGCvkv79HBjUo3PORv6bG-fdQp5UQo8ZMvd5lobmjbMna21JCBp1ITm3QSAPy4gdvfCyzxEziVyRcKwXU45ky3g7wFnyNxr9HtzX6yDJn1LlWdhChNR4uom05U5mbIDSChHAd52nWtzNb6dvdupIKCSR1XN_jF28y1Wu4aMbmVRXAn0X87Qok93KiHMoU_wydWuG5zl-ihqQ-1RA

Generate HMAC Sha256 in python 3

I write code to verify an HMAC Auth incoming POST request with JSON to our API. The HMAC I received is OD5ZxL4tdGgWr78e9vO3cYrjuOFT8WOrTbTIuuIH1PQ=
When I try to generate it by my self using Python, it is always different.
Here is the JSON request I received:
{
"shipper_id": 4841,
"status": "Cancelled",
"shipper_ref_no": "",
"tracking_ref_no": "",
"shipper_order_ref_no": "",
"timestamp": "2018-05-23T15:13:28+0800",
"id": "61185ecf-3484-4985-b625-ffe30ba36e28",
"previous_status": "Pending Pickup",
"tracking_id": "NVSGBHINK000000001"
}
And the client secret is 817a3723917f4c7fac24b1f1b324bbab.
The HMAC secret I received is OD5ZxL4tdGgWr78e9vO3cYrjuOFT8WOrTbTIuuIH1PQ=.
Here is the code when I write it in PHP:
<?php
define('CLIENT_SECRET', 'my_shared_secret');
function verify_webhook($data, $hmac_header){
$calculated_hmac = base64_encode(hash_hmac('sha256', $data, CLIENT_SECRET, true));
return ($hmac_header == $calculated_hmac);
}
$hmac_header = $_SERVER['X-NINJAVAN-HMAC-SHA256'];
$data = file_get_contents('php://input');
$verified = verify_webhook($data, $hmac_header);
error_log('Webhook verified: '.var_export($verified, true)); //check error.log to see result
?>
But I have no idea how to do it in Python 3.
In Python 3 you basically want something like the following, taken from how you handle GitHub webhook requests.
import hashlib
import hmac
secret = 'CLIENT_SECRET'
data = rsp.content # assumes you're using requests for data/sig
signature = rsp.headers['X-Something-Signature']
signature_computed = 'sha1=' + hmac.new(
key=secret.encode('utf-8'),
msg=data.encode('utf-8'),
digestmod=hashlib.sha1
).hexdigest()
if not hmac.compare_digest(signature, signature_computed):
log("Invalid payload")
If you want to recreate the hashing code from PHP to Python do it thusly:
def create_signature(key, data):
sig_hash = hmac.new(key.encode('utf8'), data.encode('utf8'), hashlib.sha256).digest()
base64_message = base64.b64encode(sig_hash).decode()
return base64_message
This will create the signature that should match what your PHP code is creating. Just compare the signature to what is sent in the header.
from collections import OrderedDict
params = orderedDict()
params["shipper_id"] = 4841
params["status"] = "Cancelled"
params["shipper_ref_no"] = ""
params["tracking_ref_no"] = ""
params["shipper_order_ref_no"] = ""
params["timestamp"] = "2018-05-23T15:13:28+0800"
params["id"] = "61185ecf-3484-4985-b625-ffe30ba36e28"
params["previous_status"] = "Pending Pickup"
params["tracking_id"] = "NVSGBHINK000000001"
mes = json(params, separator = (";",",")).highdigest()
sighnature = hmac.new(mes, sha256)
# separators = (";",",") - i'm not shure
params['sighnature'] = sighnature
r = response.post(url,params,sighnature)
print(r.text())

Categories

Resources