Skip to main content
Associate
April 17, 2026
Question

RSA encryption on STM32WL55JC - cannot decrypt on PC using Python

  • April 17, 2026
  • 1 reply
  • 102 views

Dear Community, dear STMicro,

 

I'm trying to encrypt data on an STM32WL55JC device using the STM32CubeExpansion_Crypto_V4.5.0 library and then using Python on the PC to decrypt it again.

I've taken the example code for RSA encryption from the library

STM32CubeExpansion_Crypto_V4.5.0/Projects/NUCLEO-WL55JC/Applications/RSA/PKCS1v2.2_EncryptDecrypt

and adapted it.

I've created an RSA 2048 bit key on my PC using this code:

private_key = rsa.generate_private_key(
 public_exponent=65537,
 key_size=2048)
pem = public_key.public_numbers()
public_exponent = ["0x%02s" % e[i:i+2] for i in range(0, len(e), 2)]
n = "%X" % pem.n
n = "0" + n if len(n) % 2 == 1 else n
modulus = ["0x%02s" % n[i:i+2] for i in range(0, len(n), 2)]
f = open("pub.c", "w")
f.write("public_exponent[] = {%s}\n" % ", ".join("%s" % s for s in public_exponent))
f.write("modulus[] = {%s}" % ", ".join("%s" % s for s in modulus))
f.close()

 

From this code I get something like this

public_exponent[] = {0x01, 0x00, 0x01}
modulus[] = {
 0x99, 0x88, 0x14, 0xB0, 0xEF, 0xE1, 0xD4, 0xED, 0xC4, 0x69, 0x24, 0x56, 0x17, 0xAC, 0xC2, 0xF5,
 0xC6, 0xB5, 0x80, 0x36, 0x38, 0x0A, 0x71, 0xDD, 0x31, 0x1A, 0xD8, 0xEE, 0x87, 0xC6, 0x80, 0x13, 
 0x63, 0xB2, 0x7A, 0xCC, 0xF8, 0xE3, 0xE5, 0x24, 0x8E, 0x33, 0xBF, 0xA4, 0x59, 0x98, 0x6E, 0xA3,
 0x4B, 0x79, 0x67, 0x6F, 0x10, 0xA8, 0x78, 0x7B, 0xA3, 0x53, 0x50, 0x19, 0x07, 0x43, 0x8C, 0xC5, 
 0xF3, 0x9F, 0x82, 0x75, 0x6E, 0xA0, 0x68, 0xD8, 0x7C, 0xFF, 0x13, 0xD0, 0xF8, 0x3A, 0xDD, 0xE5, 
 0x7A, 0xAE, 0x1B, 0xAC, 0xDC, 0xCE, 0xE9, 0x11, 0x42, 0xA8, 0x02, 0x38, 0xAE, 0x50, 0x91, 0x33, 
 0x57, 0x0C, 0x32, 0x23, 0x2F, 0x0F, 0x99, 0xD9, 0xD0, 0xCB, 0x11, 0x26, 0xD5, 0xC6, 0xA3, 0x87, 
 0x1A, 0xFD, 0xA5, 0xA3, 0xB4, 0xE8, 0xAB, 0x70, 0x98, 0x89, 0x74, 0xB3, 0xDC, 0xB6, 0xF6, 0x81, 
 0x3A, 0x5D, 0x09, 0xC6, 0x8C, 0x2D, 0xF0, 0x9E, 0x2A, 0xB0, 0x45, 0x73, 0x90, 0xAB, 0xC1, 0xB3, 
 0x22, 0xE9, 0xC6, 0x4B, 0xB7, 0x36, 0x08, 0xE2, 0xAD, 0xE6, 0xA3, 0xC2, 0x3E, 0xBE, 0x5E, 0x92, 
 0x17, 0x5B, 0x48, 0xAC, 0x0D, 0x6D, 0x6D, 0xE2, 0xF6, 0x21, 0x60, 0x27, 0x56, 0x59, 0x11, 0x4C, 
 0x2E, 0x10, 0xB7, 0x12, 0x73, 0x81, 0xC6, 0xA4, 0x9D, 0x46, 0x64, 0x5F, 0x73, 0xAA, 0xA5, 0xDE, 
 0xDE, 0x7E, 0x83, 0x17, 0xAA, 0x61, 0x8B, 0x99, 0xA5, 0xF7, 0x06, 0x76, 0xF8, 0x55, 0x74, 0x02, 
 0x56, 0x36, 0x45, 0x35, 0x1A, 0x1B, 0x8C, 0x48, 0x65, 0x86, 0x04, 0xC7, 0x3E, 0xA3, 0x6F, 0xEE,
 0x34, 0x52, 0x8E, 0xE2, 0x46, 0x10, 0x3E, 0x88, 0xB4, 0x85, 0x48, 0x4F, 0x43, 0xE3, 0x18, 0x24, 
 0xC3, 0x52, 0x40, 0x88, 0x7B, 0x9C, 0x2F, 0xE3, 0x54, 0x62, 0xBD, 0xEF, 0x00, 0xE7, 0xCB, 0x8B
}

I use this code to create the public key within the STM32 and the encrypt the message. Therefore I've written a function "rsa_encrypt()" for the STM32 following the example code from the Cryptolib:

uint8_t rsa_encrypt(uint8_t *message, size_t messageSize, uint8_t *encrypted, size_t *encryptedSize) {
 uint8_t seed[256];
 cmox_rsa_retval_t retval;

 cmox_rsa_construct(&Rsa_Ctx, CMOX_RSA_MATH_FUNCS, CMOX_MODEXP_PUBLIC, Working_Buffer, sizeof(Working_Buffer));


 /* Fill in RSA key structure using the public key representation */
 retval = cmox_rsa_setKey(&Rsa_Key, /* RSA key structure to fill */
 modulus, sizeof(modulus), /* Key modulus */
 public_Exponent, sizeof(public_Exponent)); /* Public key exponent */

 /* Verify API returned value */
 if (retval != CMOX_RSA_SUCCESS)
 {
 APP_LOG(TS_OFF, VLEVEL_M, "setKey failed %d\r\n", retval);
 return 0;
 }

 rnd_generateRandomNumber(seed, sizeof(seed));

 /* Compute directly the encrypted message passing all the needed parameters */
 retval = cmox_rsa_pkcs1v22_encrypt(&Rsa_Ctx, /* RSA context */
 &Rsa_Key, /* RSA key to use */
 message, messageSize, /* Message to encrypt */
 CMOX_RSA_PKCS1V22_HASH_SHA1, /* Hash method to use */
 seed, sizeof(seed), /* Random seed */
 NULL, 0, /* No label */
 encrypted, encryptedSize); /* Data buffer to receive encrypted text */

 /* Verify API returned value */
 if (retval != CMOX_RSA_SUCCESS)
 {
 APP_LOG(TS_OFF, VLEVEL_M, "encrypt failed %d\r\n", retval);
 return 0;
 }

 return 1;
}

 

This code works without problems and generates a ciphertext like this when passing "Hello", using a randomly generated seed of 256 bytes size and a working buffer of 7000 bytes. This is the ciphertext, that I got:

44 F4 72 7E 1C AC 42 8A 57 5B 7F B1 3B 93 2E E6 
5A 9B 43 64 DE 66 46 4A 95 15 BD 95 B1 A6 C4 7D 
11 38 B3 12 80 4B F0 FD 84 98 D6 AD 2A 10 7C 9F 
6D C7 09 BE B0 E1 68 1E C1 90 2E B9 8D 9E EB 14 
13 AF 25 07 09 7B 15 EF 7C 24 6D 39 94 03 54 97 
8D 62 69 75 8C 44 17 38 E8 C2 44 68 25 F4 10 33 
20 71 D2 DC 3D 68 5D 71 EF D2 8A CA 8C 0A A7 CA 
8B E0 AA 3E BA FE F6 1E D2 31 63 64 F9 5C D0 FA 
6B 85 5F 77 6B CA 17 42 E1 2A DD 49 F8 E4 45 C6 
29 E0 E4 F8 48 62 A8 9B 98 80 2E 6B 13 5A 87 88 
95 DE 44 F4 CA AB 06 D8 A0 2B 76 34 D9 6D 07 91 
51 4A 34 35 02 DF 26 EC 26 AB BF 7C 1C 02 70 C3 
A8 64 F1 29 BF CE 04 53 05 0E 5F 2F 4B CE AA 76 
1E BA DB 80 0F E0 23 CF A3 A9 3C F6 72 7B C6 1A 
64 48 FA 7B 3B 3B EA 83 67 8B 78 27 B5 CB BD 64 
7A 8C CF A4 AB 4B B3 EC DF 5E 33 68 77 0F 12 24 

Then I try to decrypt the ciphertext on the PC using Python and the private key that I have created earlier using the Python script.

 lines = <ciphertext above>.split("\n")
 for line in lines:
 enc_data += line.strip().split(" ")
 ciphertext = bytes([int(n, 16) for n in enc_data])
 print(ciphertext)
 print("len: %d" % len(ciphertext))

 f = open("private_key.pem", "rb")
 priv_key_pem = f.read()
 f.close()

 print(priv_key_pem)

 private_key = serialization.load_pem_private_key(
 priv_key_pem,
 password=None,
 )

 plaintext = private_key.decrypt(
 ciphertext,
 padding.OAEP(
 mgf=padding.MGF1(algorithm=hashes.SHA1()),
 algorithm=hashes.SHA1(),
 label=None
 )
 )
 print(plaintext)

I would assume that I get the plain text "Hello" back, but I get an error instead:

 plaintext = private_key.decrypt(
 ^^^^^^^^^^^^^^^^^^^^
ValueError: Decryption failed

 

I''ve really tried a lot of different variations, I suspect that something is wrong with the padding used by the Cryptolib. As you can see, Python would use OAEP (optimal asymmetric encryption padding) with MGF1 as a mask generation function using SHA1 as Hash function and also use SHA1 as has algorithm.

In the call to the encrypt function on the STM32 I don't have a parameter for a mask generation function, instead I have a "seed" parameter. This seems to be incompatible to the decryption procedure used in Python.
Is it OAEP that the Cryptolib is using when encrypting with PKCS#1v2.2? Or is it using a different padding algorithm? Which one?

Or is there another problem that I don't see?

Your help is very much appriciated!

Kind regards,
Bernhard

1 reply

Technical Moderator
April 17, 2026

Hello @bgoedel, and Welcome to ST Community!

I believe the most likely reason for your failure is that your STM32 code passes a 256-byte seed, while OAEP with SHA-1 expects a 20-byte seed.

Best regards,

To improve visibility of answered topics, please click 'Accept as Solution' on the reply that resolved your issue or answered your question.
bgoedelAuthor
Associate
April 20, 2026

Hello @STackPointer64 ,

thanks for your reply! I reduced the seed size to 20 bytes but I still get the "ValueError: Decryption failed" when trying to decrypt the encrypted data on the PC using the Python program.

Does the STM32 Cryptolib even do OAEP with SHA-1?

I've written a Python code to also encrypt data (so, the part that actually shall be done by the STM32 Cryptolib) just to check whether I could decrypt at least that. The Python code to encrypt data looks like this

 rsaPublicNumbers = rsa.RSAPublicNumbers(publicExponent, modulus)
 public_key = rsaPublicNumbers.public_key()
 encrypted = public_key.encrypt(b"Hello",padding=padding.OAEP(padding.MGF1(algorithm=hashes.SHA1()), algorithm=hashes.SHA1(), label = None))
 print(encrypted)
 f = open("enc.txt", "w")
 for i in range(0, len(encrypted), 16):
 f.write("%s\n" % " ".join("%02X" % i for i in encrypted[i:i+16]))
 f.close()

Now, as you can see, I use the same numbers (publicExponent and modulus) from the public key of my generated key pair to construct the public_key that I can use to encrypt the string.

However, the call to encrypt() has 2 crucial parameters:

  1. the padding
    an instance of OAEP is passed
  2. the algorithm
    an instance of SHA1 is passed

In order to instantiate the OAEP I need to pass the mask generation function to be used - currently, there is only one, namely the MGF1. But the MGF1 required also a parameter - the algorithm to be used, which - AGAIN - is the SHA1.

This makes me think: when I call encrypt() on the STM32 Cryptolib, I only need to pass the SHA1, which is - I assume - the algorithm used by the encryption itself. I don't need to pass an MGF or an algorithm used by the MGF.

That's why I wonder: Does the STM32 Cryptolib even do OAEP using MGF1 with SHA-1?

 

In my desparation to get the asymmetric encryption working, I resorted back to PKCS#1v1.5 although it is not recommended anymore because it seems to be inherently unsafe and is only supported for legacy reasons. However, even with 1.5 it does not work, what I get here is no error but always an empty deciphered text.

 

What do I do wrong?

Best regards,

Bernhard

Technical Moderator
April 23, 2026

Hello again,

It appears that your issue was possibly related to endianness. I modified the example you referenced to encrypt a simple “Hello” message after being provided with a modulus and public exponent generated with a Python script. I also created another script that decrypts the ciphertext dumped by the STM32. All the scripts are available in the Scripts folder within the project.

Best regards,

To improve visibility of answered topics, please click 'Accept as Solution' on the reply that resolved your issue or answered your question.