for mostly educational purposes I am trying to understand how the key wrap algorithm works for encryption keys on RT117x.
I refer to section 11.5.2 of the Security Reference Manual, where algorithms are given in c for both wrapping and unwrapping keys.
Specifically: I have a properly functioning board with an encrypted FW, I have the wrapped keyblob file that it uses, and I have the KEK that I used to create said keyblob.
The key used, given as input to the image_enc tool is ffeeddccbbaa99887766554433221100
What I want to try to do is to try to unwrap "by hand" the keyblob
I transcribed the C functions do_aes128_key_unwrap() which doesn't work though, I tried some key permutations:
00112233445566778899aabbccddeeff
3322110077665544bbaa9988ffeeddcc
ffeeddccbbaa99887766554433221100
ccddeeff8899aabb4455667700112233
To implement the InvCipher() function (not described in the manual) I used this:
#include <openssl/evp.h>
void InvCipher(unsigned char *in, unsigned char *expanded_kek, int rounds, unsigned char *out) {
EVP_CIPHER_CTX *ctx;
int len;
ctx = EVP_CIPHER_CTX_new();
EVP_DecryptInit_ex(ctx, EVP_aes_128_ecb(), NULL, expanded_kek, NULL);
EVP_DecryptUpdate(ctx, out, &len, in, 16);
EVP_CIPHER_CTX_free(ctx);
}
Is this correct? If not, could you illustrate to me what the implementation of the function should be?
I used ECB since this function was created to decipher a single block, though if it's wrong let me know.
Another question: the manual refers in multiple places to RFC3394. I find these lines in the algorithm comments, though:
// step 1: initialize variables
// set A = C[0]
// for i = 1 to n
// R[i] = C[i]
// step 2: calculate intermediate values
// for j = 5 to 0
// for i = n to 1
// B = AES‐1(K, (A ^ (n*j+i) | R[i])
// A = MSB(64, B)
// R[i] = LSB(64, B)
// step 3: output the results
// if A == IV
// then
// for i = 1 to n
// P[i] = R[i]
// else
// return an error
But in section 2.2.2 of RFC3394 the same steps are described differently:
1) Initialize variables.
Set A[s] = C[0] where s = 6n
For i = 1 to n
R[s][i] = C[i]
2) Calculate the intermediate values.
For t = s to 1
A[t-1] = MSB(64, AES-1(K, ((A[t] ^ t) | R[t][n]))
R[t-1][1] = LSB(64, AES-1(K, ((A[t]^t) | R[t][n]))
For i = 2 to n
R[t-1][i] = R[t][i-1]
3) Output the results.
If A[0] is an appropriate initial value (see 2.2.3),
Then
For i = 1 to n
P[i] = R[0][i]
Else
Return an error
So is the algorithm described in the manual adherent to RFC3394 or not?
Finally, I found the sources of a version of 'image_enc' (here https://community.nxp.com/t5/i-MX-RT/image-enc2-zip-download/m-p/1174943#M10980) which though doesn't seem related to the version I have (Bundled in Provisioning tool 6)
Package: image_enc
Version: 1.0.0
Description: AES encryption utility
License: LA_OPT_NXP_SOFTWARE_License
License description: NXP proprietary
Distribution Type: Binary
Location: \tools\image_enc
can you get me the sources of this version as well?
best regards
Max
Hi @mastupristi ,
1. InvCipher() needs to be implemented as below:
Please kindly refer to https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197-upd1.pdf for more details.
2. Regarding the comments between RM and RFC3394 , actually the comments from RM is an index based operation which is also specified in RFC3394:
3. Regarding the source of 'image_enc' , please kindly refer to https://github.com/JayHeng/NXP-MCUBootUtility/tree/master/tools/image_enc/code for details.
Hope that helps,
Have a great day,
Kan
-------------------------------------------------------------------------------
Note:
- If this post answers your question, please click the "Mark Correct" button. Thank you!
- We are following threads for 7 weeks after the last post, later replies are ignored
Please open a new thread and refer to the closed one, if you have a related question at a later point in time.
-------------------------------------------------------------------------------
Please help me! I'll share the otfad_keyblob.bin file with you (it's an "easy" key
test anyway). Can you try to write the sources (or a python script) to unwrap it (try them) and share the code with me?
I generated the file with this command line:
/opt/nxp/MCUX_Provi_v6/bin/tools/image_enc/linux/amd64/image_enc \
ifile=Infile_nopadding.bin \
ofile=Outfile.enc.bin \
base_addr=0x30001000 \
kek=ffeeddccbbaa99887766554433221100 \
otfad_arg=[00112233445566778899aabbccddeeff,0123456789abcdef,0x30001000,0x1c000] \
otfad_ctx_lock=0,0,0,0 \
is_boot_image=0 \
hw_eng=otfad
In the meantime, I delved into SPSDK, and wrote another python script:
import argparse
import sys
from cryptography.hazmat.primitives import keywrap
if __name__ == '__main__':
parser = argparse.ArgumentParser(conflict_handler="resolve")
parser.add_argument("infile", help="input file name")
parser.add_argument("kek", help="kek")
args = parser.parse_args()
with open(args.infile, "rb") as inFile:
wrapped_ciphertext = inFile.read()[:64]
# print(' '.join(['%02x' % x for x in wrapped_ciphertext]))
kek = bytearray.fromhex(args.kek)
print(' '.join(['%02x' % x for x in kek]))
try:
unwrapped_plaintext = keywrap.aes_key_unwrap(kek, wrapped_ciphertext)
sys.stdout.write(unwrapped_plaintext) # you must pipe the stdout of this script to `hd`
except:
print("Failed to unwrap")
sys.exit(1)
But even then it doesn't work
best regards
Max
I tried to take a look at spsdk. It's not easy for me to figure out, but in the otfad.py file (https://github.com/nxp-mcuxpresso/spsdk/blob/09c711a8fd4c54f126a7dfe1b3ae8bb361c5473e/spsdk/utils/cr...) there's a call to aes_key_wrap(), which links back to symmetric.py (https://github.com/nxp-mcuxpresso/spsdk/blob/09c711a8fd4c54f126a7dfe1b3ae8bb361c5473e/spsdk/crypto/s...), and I see that a few lines below there's also the function to unwrap.
Both are part of the cryptography module
The unwrap is documented here: https://cryptography.io/en/latest/hazmat/primitives/keywrap/#cryptography.hazmat.primitives.keywrap....
I a tried to see if there was already an example, but I didn't find anything useful, so I tried a little by myself without success.
This i the script:
from Cryptodome.Cipher import AES
import argparse
import sys
def InvCipher(in_data, expanded_kek):
cipher = AES.new(expanded_kek, AES.MODE_ECB)
out_data = cipher.decrypt(in_data)
return out_data
def do_aes128_key_unwrap(wrapped_ciphertext, expanded_kek):
N = 6
iv = [0xa6] * 8 # 64-bit initialization vector
a = wrapped_ciphertext[:8]
r = [wrapped_ciphertext[8 * (i + 1):8 * (i + 2)] for i in range(N)]
for j in range(5, -1, -1):
for i in range(N, 0, -1):
b = bytearray(a[:] + r[i - 1])
b[7] = b[7] ^ (N * j) + i
b = InvCipher(bytes(b), expanded_kek)
a = b[:8]
r[i - 1] = b[8:]
unwrapped_plaintext = a + bytes([item for sublist in r for item in sublist])
if unwrapped_plaintext[:8] == iv:
return unwrapped_plaintext
else:
return None
if __name__ == '__main__':
parser = argparse.ArgumentParser(conflict_handler="resolve")
parser.add_argument("infile", help="input file name")
parser.add_argument("kek", help="kek")
args = parser.parse_args()
with open(args.infile, "rb") as inFile:
wrapped_ciphertext = inFile.read()
kek = bytearray.fromhex(args.kek)
unwrapped_plaintext = do_aes128_key_unwrap(wrapped_ciphertext, kek)
if None != unwrapped_plaintext:
sys.stdout.write(unwrapped_plaintext)
else:
print("Error unwrapping")
Hi @mastupristi,
I will ask SPSDK developers regarding key wrapping function.
Lots of people is already on holidays, so it might take some time.
Thank you very much for your understanding.
Best Regards,
Martin H.
Hello Martin,
Wow, I must say, your timing is almost as impeccable as Santa's sleigh on a foggy Christmas Eve! It took a neat four weeks to tell me that everyone's on holiday for Christmas. Here I was, thinking my calendar was playing tricks on me, because last I checked, four weeks ago wasn't quite the season to be jolly just yet.
I can't help but feel a bit like I'm in a holiday comedy special - you know, the one where the main character waits forever for a plot twist, only to find out it's just another commercial break. I do appreciate the update, though it's a bit like receiving a Christmas gift in January.
No rush though, I guess good things come to those who wait... and wait... and wait some more. Looking forward to hearing from you in the New Year – or maybe Easter?
Cheers and happy holidays,
Max
Hi @mastupristi,
the full OTFAD keyblob unwrap function is not available in SPSDK. I have created a ticket to add this function for verification purposes. Difficult to estimate when it will be integrated as the team is busy with high priority tasks.
Let me explain how it works:
Current SPSDK contains aes_key_wrap/unwrap present in the spsdk->crypto->symmetric.py
aes_key_wrap is used in the "def export" function available in spsdk->utils-crypto->otfad.py file.
The OTFAD configuration structure is composed of 4 blocks to support 4 keys along with corresponding memory regions.
In export function each block of 40 bytes is wrapped, then it is aligned to 64 bytes by zeros. This is called 4 times. In total the OFAD keyblobk is 256 bytes, concatenation of 4 wrapped blocks + padding.
It means that aes_key_unwrap must be call 4 times to unwrap only the 40 bytes per each blocks (need to points to the correct data within the keyblob).
OTFAD keyblob:
-------------------------------
40 bytes of wrapped data - block 1 - > this data must be unwrapped
16 bytes of zeros
-------------------------------
40 bytes of wrapped data - block 2 - > this data must be unwrapped
16 bytes of zeros
-------------------------------
40 bytes of wrapped data - block 3 - > this data must be unwrapped
16 bytes of zeros
-------------------------------
40 bytes of wrapped data - block 4 - > this data must be unwrapped
16 bytes of zeros
-------------------------------
Let me know if you have any questions.
Best Regards,
Martin H.
Trying to approach the problem from a different point, I made other findings that contradict the documentation. Please read all this post
I made a point. To validate my algorithms, it is preferable to test on the "unused" contexts. And it's most likely better to validate a wrapping procedure to get useful information for unwrapping. In fact by wrapping I have both the input data (the plain-text data structure that in unused contexts should be all 0), I have the ciphertext and the KEK key.
I will proceed in the chronological order that I followed.
the command line that was used to create the keyblob used image_enc:
I highlighted the second context that is not used, and all fields are 0
at this point I wanted to try to do the same thing but using spsdk taken from github (nxpimage.py, version 2.0.1, commit 0144584ce8), trying to follow the execution in debug.
I added some print within the code to "see" KEK, plain-text and cipher text
In doing this I realized that the plaintext is not all 0 but has 4 non-zero bytes:
the cipher text is exactly that contained in the keyblob file produced by image_enc. However, the 40-byte plaintext has the last 4 non-zero bytes
the place where these bytes are populated is in the otfad.py file:
The only documentation I have found so far is this:
It is possible to use it for testing but they recommend using random values, and this, as we shall see, contradicts the documentation. In my case zero_fill is a 4-byte array 0-filled while crc is None.
This isn't documented anywhere else (from the research I've done so far), in fact, on the Security Reference Manual it is extensively written that padding must be 0:
I have highlighted the padding bytes, and in red those that are not actually 0.
so other questions to add to the others are related to this:
regards
Max
Hi @martin_hrncarek,
in these two weeks have you had a chance to elaborate on what I said in my last post?
I lean toward believing that it is the documentation that needs to align with what SPSDK has implemented. After all, the implementers of SPSDK will have followed some specification.
Please answer my questions at the bottom of the previous post, and I suggest you contact the team responsible for documentation to solicit the necessary fixes
regards
Max
Current SPSDK contains aes_key_wrap/unwrap present in the spsdk->crypto->symmetric.py
yes I knew that, in fact I wrote my last attempt based on that.
They use this cryptography.hazmat:
the description of the parameters is clear and I wanted to try it too:
import argparse
import sys
from cryptography.hazmat.primitives import keywrap
if __name__ == '__main__':
parser = argparse.ArgumentParser(conflict_handler="resolve")
parser.add_argument("infile", help="input file name")
parser.add_argument("kek", help="kek")
parser.add_argument("bytes", help="bytes", type=int)
args = parser.parse_args()
with open(args.infile, "rb") as inFile:
wrapped_ciphertext = inFile.read()[:args.bytes]
kek = bytearray.fromhex(args.kek)
try:
unwrapped_plaintext = keywrap.aes_key_unwrap(kek, wrapped_ciphertext)
sys.stdout.write(unwrapped_plaintext) # you must pipe the stdout of this script to `hd`
except:
print("Failed to unwrap")
sys.exit(1)
however it does not work.
My keyblob (note that this is working on my EVK using the KEK ffeeddccbbaa99887766554433221100) is like this:
00000000 3f e3 81 f6 e8 a1 47 d2 fa 6c ca 69 53 c6 1c 80 |?.....G..l.iS...|
00000010 1f 4a a3 ea f9 4f 10 f2 90 71 3c 52 5b 39 a6 a4 |.J...O...q<R[9..|
00000020 ba 19 b1 3b b5 48 ae 3f 73 b6 ac c6 60 6a 2a b2 |...;.H.?s...`j*.|
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000040 79 0f 0d c7 d4 4d 61 a1 de 7f f4 09 45 3e fa f0 |y....Ma.....E>..|
00000050 cb 74 01 13 0e 12 12 c0 56 1f bb 89 e3 73 ac 44 |.t......V....s.D|
00000060 11 a2 40 d1 52 1d 7f ec b0 4b 40 d5 16 3b 84 0c |..@.R....K@..;..|
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000080 79 0f 0d c7 d4 4d 61 a1 de 7f f4 09 45 3e fa f0 |y....Ma.....E>..|
00000090 cb 74 01 13 0e 12 12 c0 56 1f bb 89 e3 73 ac 44 |.t......V....s.D|
000000a0 11 a2 40 d1 52 1d 7f ec b0 4b 40 d5 16 3b 84 0c |..@.R....K@..;..|
000000b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
000000c0 79 0f 0d c7 d4 4d 61 a1 de 7f f4 09 45 3e fa f0 |y....Ma.....E>..|
000000d0 cb 74 01 13 0e 12 12 c0 56 1f bb 89 e3 73 ac 44 |.t......V....s.D|
000000e0 11 a2 40 d1 52 1d 7f ec b0 4b 40 d5 16 3b 84 0c |..@.R....K@..;..|
000000f0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
you can clearly see the 4 blocks, where only the first one has values, and the others are not used (I think they are populated with 0). Padding to 0 is evident. The encrypted part is evidently in the first 48 bytes of the block.
So to the aes_key_unwrap() function I pass 48 bytes and the kek. But despite this it fails to decode.
The full OTFAD keyblob unwrap function is not available in SPSDK. I have created a ticket to add this function for verification purposes. Difficult to estimate when it will be integrated as the team is busy with high priority tasks.
OK, I'm glad that in the SPSDK it was asked to add such a function, but in the meantime can you please review my python source and tell me where I'm wrong?
regards
Max