cancel
Showing results for 
Search instead for 
Did you mean: 

RSA encryption on STM32WL55JC - cannot decrypt on PC using Python

bgoedel
Associate II

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 ACCEPTED SOLUTION

Accepted Solutions
bgoedel
Associate II

Dear @STackPointer64 ,

thank you for your help!

Today I resolved the riddle using your input. It was not the endianess, it was a triviality!

I used the API functions of the cmox (Cryptolib) without calling `cmox_initialize()` first! That was it. Everything of my code works now that I call this function in advance of using other API functions or the cmox library!

Your code helped in a way that I found out that the RSA Example code delivered ciphertext that could be decrypted your Python script. But my STM Code using the same public key (Modulus) delivered a ciphertext that even your Python script could not decrypt.

Now, it was clear that the problem was in my STM32 code, so I searched for differences to the example code. It was not easy to spot because the example code does a lot more like decrypting and using the CRT notation for the RSA keys, thing that I don't need.

But I found it and using the sequence:

cmox_initialize()
cmox_rsa_construct()
cmox_rsa_setKey()
cmox_rsa_pkcs1v22_encrypt()

deliveres a ciphertext that can be decrypted using Python3 cryptography.hazmat library using 

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

Probably it would be a good idea to deliver a PC-side script along with the Example code that allows:

  • generating a key, exporting the key in the format that can be digested by the STM32 cmox library,
  • that can also be used to decrypt ciphertext generated by the Example code on the STM32 or
  • encrypt text that can be decrypted by the Example code on the STM32.

BTW, my code works also when using a seed with 256 Bytes length that is generated using the TRNG hardware that is available on the STM32WL55JC.

Kind regards,
Bernhard

View solution in original post

4 REPLIES 4
STackPointer64
ST Employee

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.

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

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.
bgoedel
Associate II

Dear @STackPointer64 ,

thank you for your help!

Today I resolved the riddle using your input. It was not the endianess, it was a triviality!

I used the API functions of the cmox (Cryptolib) without calling `cmox_initialize()` first! That was it. Everything of my code works now that I call this function in advance of using other API functions or the cmox library!

Your code helped in a way that I found out that the RSA Example code delivered ciphertext that could be decrypted your Python script. But my STM Code using the same public key (Modulus) delivered a ciphertext that even your Python script could not decrypt.

Now, it was clear that the problem was in my STM32 code, so I searched for differences to the example code. It was not easy to spot because the example code does a lot more like decrypting and using the CRT notation for the RSA keys, thing that I don't need.

But I found it and using the sequence:

cmox_initialize()
cmox_rsa_construct()
cmox_rsa_setKey()
cmox_rsa_pkcs1v22_encrypt()

deliveres a ciphertext that can be decrypted using Python3 cryptography.hazmat library using 

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

Probably it would be a good idea to deliver a PC-side script along with the Example code that allows:

  • generating a key, exporting the key in the format that can be digested by the STM32 cmox library,
  • that can also be used to decrypt ciphertext generated by the Example code on the STM32 or
  • encrypt text that can be decrypted by the Example code on the STM32.

BTW, my code works also when using a seed with 256 Bytes length that is generated using the TRNG hardware that is available on the STM32WL55JC.

Kind regards,
Bernhard