My application to generate SHA256 hash value for the "xx_xx.hex " file and then generate RSA signature for hash . SHA256 value generated by X-Cube-CRYPTOLIB and python script both are same. But RSA signature is not same.
i created 1024 key pair by using "openssl".Hash value is generated by X-Cube-CRYPTOLIB is in decimal format , But it is equivalent to generated hash value (hex format) by python script .
Generated SHA256 value is: 5b4c333945c47a952bfe1571e5b58b4674985fa754f19abcb5739f4c2dda433b
But the signature which is generated X-Cube-CRYPTOLIB decimal values are not equivalent to the generated signature (hex format ) by python script.
Below is the result of python script;
Hexadecimal signature: 72869266541a76bc2a73437e53c1c917e4b2dd323f8b9735e44f6d600e7fe414c0899e41e0a89c557bcd3673dac2084409fabb0ef52574108363fdd3bdc44d2239371fe3e0617966f93964db42d12ad91b254b70af7add8cd1043d7f6a5857ea834c885480d5ac2d792c514233fcc62eea3149fc8fa50e0e2579f780ff6e1c37
Please find the below image to check signature generated by X-Cube-CRYPTOLIB, it is not complete.But just for reference we can see few bytes. computed singature X-Cube-CRYPTOLIB
Python script:
import hashlib
import sys
from intelhex import IntelHex
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import os
import binascii
# Path to the hex file you want to load
file_path = 'W:/xxx/xxx/xxx_input.hex'
privkey_path = 'W:/xxx/xxx/rivate_1024key.pem'
pubkey_path = 'W:/xxx/xxx/public_1024key.pem'
output_file = 'W:/xxx/xxx//Hash.txt'
outhex_file = 'W:/xxx/xxx/xxx_output.hex'
# Define the ranges() function
def ranges(addresses):
start = addresses[0]
end = addresses[0]
for address in addresses[1:]:
if address == end + 1:
end = address
else:
yield start, end
start = address
end = address
yield start, end
# Load the Intel Hex file
hex = IntelHex(file_path)
# Get the sorted list of addresses
addresses = hex.addresses()
addresses.sort()
# Iterate over address ranges and extract the data
data_list = list(ranges(addresses))
for start, end in data_list:
size = end - start + 1
data = list(hex.tobinarray(start=start, size=size))
# Convert the data to hexadecimal format
hex_data = ''.join(format(byte, '02x') for byte in data)
data_bytes = bytes.fromhex(hex_data)
hasher = hashlib.sha256()
hasher.update(data_bytes)
hash_value = hasher.hexdigest()
# Print the hash value in hexadecimal format
print("SHA256 Hash Value:", hash_value)
# Digest to be signed
# Convert the hash value to bytes
digest_bytes = bytes.fromhex(hash_value)
# Load the private key from file
try:
with open(privkey_path, 'rb') as f:
private_key = serialization.load_pem_private_key(
f.read(),
password=None,
backend=default_backend()
)
print("private_key:", private_key)
# If no exception occurred, the private key was loaded successfully
print("Private key loaded successfully.")
# Use the private key for signing or other operations
except Exception as e:
print("Error loading private key:", e)
# Save the digest bytes to a file
with open(output_file, 'wb') as file:
file.write(digest_bytes)
print("Digest:", digest_bytes)
# Sign the digest with the private key
signature = private_key.sign(
digest_bytes,
padding.PKCS1v15(), # Use PKCS1v15 padding scheme for RSA signatures
hashes.SHA256() # Use SHA256 hashing algorithm
)
# Convert the signature to hexadecimal format if needed
hex_signature = signature.hex()
# Print the hexadecimal signature
print("Hexadecimal signature:", hex_signature)
C Code of X-Cube:
const uint8_t Message[] =
{
0xcd, 0xc8, 0x7d, 0xa2, 0x23, 0xd7, 0x86, 0xdf, 0x3b, 0x45, 0xe0, 0xbb, 0xbc, 0x72, 0x13, 0x26,
0xd1, 0xee, 0x2a, 0xf8, 0x06, 0xcc, 0x31, 0x54, 0x75, 0xcc, 0x6f, 0x0d, 0x9c, 0x66, 0xe1, 0xb6,
0x23, 0x71, 0xd4, 0x5c, 0xe2, 0x39, 0x2e, 0x1a, 0xc9, 0x28, 0x44, 0xc3, 0x10, 0x10, 0x2f, 0x15,
0x6a, 0x0d, 0x8d, 0x52, 0xc1, 0xf4, 0xc4, 0x0b, 0xa3, 0xaa, 0x65, 0x09, 0x57, 0x86, 0xcb, 0x76,
0x97, 0x57, 0xa6, 0x56, 0x3b, 0xa9, 0x58, 0xfe, 0xd0, 0xbc, 0xc9, 0x84, 0xe8, 0xb5, 0x17, 0xa3,
0xd5, 0xf5, 0x15, 0xb2, 0x3b, 0x8a, 0x41, 0xe7, 0x4a, 0xa8, 0x67, 0x69, 0x3f, 0x90, 0xdf, 0xb0,
0x61, 0xa6, 0xe8, 0x6d, 0xfa, 0xae, 0xe6, 0x44, 0x72, 0xc0, 0x0e, 0x5f, 0x20, 0x94, 0x57, 0x29,
0xcb, 0xeb, 0xe7, 0x7f, 0x06, 0xce, 0x78, 0xe0, 0x8f, 0x40, 0x98, 0xfb, 0xa4, 0x1f, 0x9d, 0x61,
0x93, 0xc0, 0x31, 0x7e, 0x8b, 0x60, 0xd4, 0xb6, 0x08, 0x4a, 0xcb, 0x42, 0xd2, 0x9e, 0x38, 0x08,
0xa3, 0xbc, 0x37, 0x2d, 0x85, 0xe3, 0x31, 0x17, 0x0f, 0xcb, 0xf7, 0xcc, 0x72, 0xd0, 0xb7, 0x1c,
0x29, 0x66, 0x48, 0xb3, 0xa4, 0xd1, 0x0f, 0x41, 0x62, 0x95, 0xd0, 0x80, 0x7a, 0xa6, 0x25, 0xca,
0xb2, 0x74, 0x4f, 0xd9, 0xea, 0x8f, 0xd2, 0x23, 0xc4, 0x25, 0x37, 0x02, 0x98, 0x28, 0xbd, 0x16,
0xbe, 0x02, 0x54, 0x6f, 0x13, 0x0f, 0xd2, 0xe3, 0x3b, 0x93, 0x6d, 0x26, 0x76, 0xe0, 0x8a, 0xed,
0x1b, 0x73, 0x31, 0x8b, 0x75, 0x0a, 0x01, 0x67, 0xd0
};
const uint8_t Known_Signature[] =
{
0x6b, 0xc3, 0xa0, 0x66, 0x56, 0x84, 0x29, 0x30, 0xa2, 0x47, 0xe3, 0x0d, 0x58, 0x64, 0xb4, 0xd8,
0x19, 0x23, 0x6b, 0xa7, 0xc6, 0x89, 0x65, 0x86, 0x2a, 0xd7, 0xdb, 0xc4, 0xe2, 0x4a, 0xf2, 0x8e,
0x86, 0xbb, 0x53, 0x1f, 0x03, 0x35, 0x8b, 0xe5, 0xfb, 0x74, 0x77, 0x7c, 0x60, 0x86, 0xf8, 0x50,
0xca, 0xef, 0x89, 0x3f, 0x0d, 0x6f, 0xcc, 0x2d, 0x0c, 0x91, 0xec, 0x01, 0x36, 0x93, 0xb4, 0xea,
0x00, 0xb8, 0x0c, 0xd4, 0x9a, 0xac, 0x4e, 0xcb, 0x5f, 0x89, 0x11, 0xaf, 0xe5, 0x39, 0xad, 0xa4,
0xa8, 0xf3, 0x82, 0x3d, 0x1d, 0x13, 0xe4, 0x72, 0xd1, 0x49, 0x05, 0x47, 0xc6, 0x59, 0xc7, 0x61,
0x7f, 0x3d, 0x24, 0x08, 0x7d, 0xdb, 0x6f, 0x2b, 0x72, 0x09, 0x61, 0x67, 0xfc, 0x09, 0x7c, 0xab,
0x18, 0xe9, 0xa4, 0x58, 0xfc, 0xb6, 0x34, 0xcd, 0xce, 0x8e, 0xe3, 0x58, 0x94, 0xc4, 0x84, 0xd7
};
const uint8_t Modulus[] =
{
0xa5, 0x6e, 0x4a, 0x0e, 0x70, 0x10, 0x17, 0x58, 0x9a, 0x51, 0x87, 0xdc, 0x7e, 0xa8, 0x41, 0xd1,
0x56, 0xf2, 0xec, 0x0e, 0x36, 0xad, 0x52, 0xa4, 0x4d, 0xfe, 0xb1, 0xe6, 0x1f, 0x7a, 0xd9, 0x91,
0xd8, 0xc5, 0x10, 0x56, 0xff, 0xed, 0xb1, 0x62, 0xb4, 0xc0, 0xf2, 0x83, 0xa1, 0x2a, 0x88, 0xa3,
0x94, 0xdf, 0xf5, 0x26, 0xab, 0x72, 0x91, 0xcb, 0xb3, 0x07, 0xce, 0xab, 0xfc, 0xe0, 0xb1, 0xdf,
0xd5, 0xcd, 0x95, 0x08, 0x09, 0x6d, 0x5b, 0x2b, 0x8b, 0x6d, 0xf5, 0xd6, 0x71, 0xef, 0x63, 0x77,
0xc0, 0x92, 0x1c, 0xb2, 0x3c, 0x27, 0x0a, 0x70, 0xe2, 0x59, 0x8e, 0x6f, 0xf8, 0x9d, 0x19, 0xf1,
0x05, 0xac, 0xc2, 0xd3, 0xf0, 0xcb, 0x35, 0xf2, 0x92, 0x80, 0xe1, 0x38, 0x6b, 0x6f, 0x64, 0xc4,
0xef, 0x22, 0xe1, 0xe1, 0xf2, 0x0d, 0x0c, 0xe8, 0xcf, 0xfb, 0x22, 0x49, 0xbd, 0x9a, 0x21, 0x37
};
const uint8_t Public_Exponent[] =
{
0x01, 0x00, 0x01
};
const uint8_t Private_Exponent[] =
{
0x33, 0xa5, 0x04, 0x2a, 0x90, 0xb2, 0x7d, 0x4f, 0x54, 0x51, 0xca, 0x9b, 0xbb, 0xd0, 0xb4, 0x47,.......};
const uint8_t P_Prime[] =
{
0xe7, 0xe8, 0x94, 0x27, 0x20, 0xa8, 0x77, 0x51, 0x72, 0x73, 0xa3, 0x56, 0x05, 0x3e, 0xa2, 0xa1,....};
const uint8_t Q_Prime[] =
{
0xb6, 0x9d, 0xca, 0x1c, 0xf7, 0xd4, 0xd7, 0xec, 0x81, 0xe7, 0x5b, 0x90, 0xfc, 0xca, 0x87, 0x4a, ..........};
const uint8_t P_Prime_Exponent[] =
{
0x28, 0xfa, 0x13, 0x93, 0x86, 0x55, 0xbe, 0x1f, 0x8a, 0x15, 0x9c, 0xba, 0xca, 0x5a, 0x72, 0xea,.............};
const uint8_t Q_Prime_Exponent[] =
{
0x1a, 0x8b, 0x38, 0xf3, 0x98, 0xfa, 0x71, 0x20, 0x49, 0x89, 0x8d, 0x7f, 0xb7, 0x9e, 0xe0, 0xa7,....};
const uint8_t Coefficient[] =
{
0x27, 0x15, 0x6a, 0xba, 0x41, 0x26, 0xd2, 0x4a, 0x81, 0xf3, 0xa5, 0x28, 0xcb, 0xfb, 0x27, 0xf5,... };
uint8_t Computed_Signature[128];
uint8_t computed_hash[CMOX_SHA256_SIZE];
const uint8_t Public_Exponent[] = {0x01, 0x00, 0x01};
uint8_t Working_Buffer[3500];
StartAddress = 0x0802A000;
EndAddress = 0x0802FF00;
/* Compute directly the digest passing all the needed parameters */
hretval = cmox_hash_compute(CMOX_SHA256_ALGO, /* Use SHA256 algorithm */
const uint8_t*)(uintptr_t)StartAddress,
(EndAddress - StartAddress + 1), /* Message to digest */
computed_hash, /* Data buffer to receive digest data */
CMOX_SHA256_SIZE, /* Expected digest size */
&computed_size); /* Size of computed digest *
/* Construct a RSA context, specifying mathematics implementation and working buffer for later processing */
cmox_rsa_construct(&Rsa_Ctx, CMOX_RSA_MATH_FUNCS, CMOX_MODEXP_PRIVATE, Working_Buffer, sizeof(Working_Buffer));
retval = cmox_rsa_setKeyCRTwithFACM(&Rsa_Key, /* RSA key structure to fill */
sizeof(Modulus) * 8, /* Private key modulus bit length */
P_Prime_Exponent, sizeof(P_Prime_Exponent), /* P prime */
Q_Prime_Exponent, sizeof(Q_Prime_Exponent), /* Q prime */
P_Prime, sizeof(P_Prime), /* P prime exponent */
Q_Prime, sizeof(Q_Prime), /* Q prime exponent */
Coefficient, sizeof(Coefficient), /* Coefficient */
Public_Exponent, sizeof(Public_Exponent)); /* Public exponent */
/* Compute directly the signature passing all the needed parameters */
retval = cmox_rsa_pkcs1v15_sign(&Rsa_Ctx, /* RSA context */
&Rsa_Key, /* RSA key to use */
computed_hash, /* Digest to sign */
CMOX_RSA_PKCS1V15_HASH_SHA256, /* Method used to compute the digest */
Computed_Signature, &computed_size); /* Data buffer to receive signature */
in script I used PKCS1v15 padding scheme for RSA signature, Which is similar to C code. But still the signatures are not same.
Please suggest what is the wrong here. might be any data format conversion or double hashing or something else?
When the signature is decrypted with the public key without removing the padding:
signature = bytes.fromhex("6bc3a06656842930a247e30d5864b4d819236ba7c68965862ad7dbc4e24af28e86bb531f03358be5fb74777c6086f850caef893f0d6fcc2d0c91ec013693b4ea00b80cd49aac4ecb5f8911afe539ada4a8f3823d1d13e472d1490547c659c7617f3d24087ddb6f2b72096167fc097cab18e9a458fcb634cdce8ee35894c484d7")
modulus = bytes.fromhex("a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137")
public_exponent = bytes.fromhex("010001")
signature_int = int.from_bytes(signature, 'big')
modulus_int = int.from_bytes(modulus, 'big')
public_exponent_int = int.from_bytes(public_exponent, 'big')
decrypted_int = pow(signature_int, public_exponent_int, modulus_int)
decrypted = decrypted_int.to_bytes(128, 'big')
print(decrypted.hex()) # 0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a05000414cd8b6538cb8e8de566b68bd067569dbf1ee2718e
a valid PKCS#1 v1.5 signature results:
0001ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a05000414cd8b6538cb8e8de566b68bd067569dbf1ee2718e
The first part of
3021300906052b0e03021a05000414 cd8b6538cb8e8de566b68bd067569dbf1ee2718e
consists of the ID of the used digest and identifies SHA-1 as the digest applied (see here). When the SHA-1 hash of the used data is generated:
import hashlib
message = bytes.fromhex("cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b62371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb769757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb061a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d6193c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0")
hash = hashlib.sha1(message).hexdigest()
print(hash) # cd8b6538cb8e8de566b68bd067569dbf1ee2718e
the hash is
cd8b6538cb8e8de566b68bd067569dbf1ee2718e
which corresponds to the second part and is therefore consistent.
This shows that the X Cube code hashes the data with SHA-1 (and not SHA-256) and signs the hash with RSA PKCS#1 v1.5 (so single hashing of the raw data), i.e. the Python counterpart can be implemented as follows:
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import serialization
# import private key (PEM key generated from the posted RSA parameters)
pkcs8pem = b'''-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKVuSg5wEBdYmlGH
3H6oQdFW8uwONq1SpE3+seYfetmR2MUQVv/tsWK0wPKDoSqIo5Tf9SarcpHLswfO
q/zgsd/VzZUICW1bK4tt9dZx72N3wJIcsjwnCnDiWY5v+J0Z8QWswtPwyzXykoDh
OGtvZMTvIuHh8g0M6M/7Ikm9miE3AgMBAAECgYAzpQQqkLJ9T1RRypu70LRHcaEB
r4hDQK75iF8qS76S6JSnJKw8VoyPl4U60HwCZsjGo8oJKfHo8RIxiEQp/E2a5V/u
iWoQznB8PtfnNORHJ6OVdFAaUyaDEJwqusq6KDwxtL0vU8PuN+NSzuNPnlA72AwG
Iq15xtzuiDVHxqOzJQJBALadyhz31NfsgedbkPzKh0q83hI/0nABgKqQR5tuSN6N
Z+0k+fGdhbonWHT1Qs0g3HI+aWM2Sh+UJUUrJppnmf0CQQDn6JQnIKh3UXJzo1YF
PqKhvAyUqnLVXG6GKWst/JZ5SMCnLLzMp+rLNXBuCaHfVaFTW9mzzDQWCzttzT7a
jmRDAkAaizjzmPpxIEmJjX+3nuCndmh5EpnN+gnvwOUHrLIe10MB71v9SL5FXq62
4WeCVYJ1gKjk6OFBUdFRCoKj8ucpAkAo+hOThlW+H4oVnLrKWnLqGQwwCJ4ZzSdK
VW82xPbhn1VLNMB3eQQnu92N0+3iRIMo84XYGzDo5Dsv/6Anhhl5AkEAl9cGCkGe
DqJxZ+DSOnhEkJztasjgpsDbhO1F5rXnmyzWAauZuifzxMh7/E98LH5AUb0+6Ir2
j2IJhqvB6n5T9Q==
-----END PRIVATE KEY-----'''
private_key = serialization.load_pem_private_key(pkcs8pem, password=None)
# sign with RSA PKCS#1 v1.5 padding and SHA-1
message = bytes.fromhex("cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b62371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb769757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb061a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d6193c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0")
signature = private_key.sign(
message,
padding.PKCS1v15(),
hashes.SHA1()
)
print(signature.hex()) # 6bc3a06656842930a247e30d5864b4d819236ba7c68965862ad7dbc4e24af28e86bb531f03358be5fb74777c6086f850caef893f0d6fcc2d0c91ec013693b4ea00b80cd49aac4ecb5f8911afe539ada4a8f3823d1d13e472d1490547c659c7617f3d24087ddb6f2b72096167fc097cab18e9a458fcb634cdce8ee35894c484d7
which provides the expected signature:
6bc3a06656842930a247e30d5864b4d819236ba7c68965862ad7dbc4e24af28e86bb531f03358be5fb74777c6086f850caef893f0d6fcc2d0c91ec013693b4ea00b80cd49aac4ecb5f8911afe539ada4a8f3823d1d13e472d1490547c659c7617f3d24087ddb6f2b72096167fc097cab18e9a458fcb634cdce8ee35894c484d7
Edit: Regarding your question from the comment:
To pass the hashed message instead of the message and disable implicit hashing, Prehashed
must be used (s. here):
from cryptography.hazmat.primitives.asymmetric import utils
...
# sign with RSA PKCS#1 v1.5 padding and SHA-1 (pass message_hash, disable implicit hashing)
signature = private_key.sign(
message_hash,
padding.PKCS1v15(),
utils.Prehashed(hashes.SHA1()) # apply Prehashed
)
where message_hash
is the hash of the message as bytes-like object that was generated e.g. as follows:
message = bytes.fromhex("cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b62371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb769757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb061a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d6193c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0")
digest = hashes.Hash(hashes.SHA1())
digest.update(message)
message_hash = digest.finalize()