RSA encryption on STM32WL55JC - cannot decrypt on PC using Python
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
