0

I have been writing my own implementation of the Advanced Encryption Standard with cipher block chaining in python3 for the last several months, mimicking the OpenSSL command line interface. I can successfully encrypt a message or binary with the OpenSSL implementation of AES and then decrypt it with my python3 program (and vice versa).

I am currently working on PBKDF1 SHA-512 Key Iterations.

OpenSSL generates the key and IV for the password “Sparky” using PBKDF1 SHA-512 like so:

OpenSSL command to use to follow along is this:

printf "hi" | openssl enc -aes-128-cbc -md sha512 -p -S 436172616D656C00 -k “Sparky” 

which means that

salt=436172616D656C00
key=D0F4428B02B161F125FF3FA1FDD98AFE 
IV=6F26A9F743FAD01BBC68417A398359A6 

In python3, the same Key and IV can be obtained like this:

import hashlib

salt = "436172616D656C00" passwd = "Sparky”.encode(“utf-8") salted = passwd + bytes.fromhex(salt) Key = hashlib.sha512(salted).hexdigest()[0:32] IV = hashlib.sha512(salted).hexdigest()[32:64]

The key and IV can be found in substrings of the SHA-512 hash.


So I’m not having trouble obtaining the Key and the IV without iterations, but I can’t figure out how to generate the next iteration of the Key (-iter 1)

According to several sources on the internet the iteration function hashes the resultant hash n times. I think something else or something additional is going though because I have tried to hash Hash#0 and the resultant hash does not contain the expected strings for the Key and IV.

So the result of the same OpenSSL command above but with -iter 1 added yields:

printf "hi" | openssl enc -aes-128-cbc -md sha512 -p -S 436172616D656C00 -k "Sparky" -iter 1

results in:

salt=436172616D656C00
key=6354F3AE1EEF92EAE4406759A5882B46 
IV=8ACE6C6AE7E1EBDEE45342D0C9087422

Hash0 (the SHA-512 hash without iterations) in this example is

d0f4428b02b161f125ff3fa1fdd98afe6f26a9f743fad01bbc68417a398359a675fe9c7bbf87315f7e4d2b8ef40e578a86eec2acdd95511329af2a9d69f59e76

If I were to take this hash which I know to be good for the 0-th iteration, and hash it again, I would get:

35841649790d6ce2d0ac2b3fe338aba5121858494006aa098f84e06f21587b77adf14f1c5c0a1530db628acfd5d6123d46e449b9619a41a5a6e32ab9230a31e4

And according to the OpenSSL command with -iter 1 set, the hash should start with

6354F3AE1EEF92EAE4406759A5882B468ACE6C6AE7E1EBDEE45342D0C9087422

If you would like to see my code, I can upload it to github or my virtual private server and share it.

Maarten Bodewes
  • 92,551
  • 13
  • 161
  • 313
B345T
  • 1
  • 1
    "I have been reverse engineering and writing the Advanced Encryption Standard" - It is a well known standard since more than 20 years. What do you mean by reverse engineering AES? Of were you reverse engineering thy Python library? Again, why? The source code of many Python libraries is open source, means you can get the source code for free and there is nothing to reverse engineer. – mentallurg Mar 28 '21 at 20:17
  • I wanted to write AES from scratch (besides the Rcon, SubBytes, and Mix Columns vectors) to see if I could. I started by programming the ShiftRows step. Then I wrote the SubBytes step, then the mixcolumns step, then key schedule. Once that was done I added cipher block chaining because it was electronic code book at the time. A month ago I added Sha512 key derivation and salting. Now I’m trying to add the key iteration feature for KDF1. I wrote my own library of code and the goal is to figure out what OpenSSL is doing when it iterates through derived keys. – B345T Mar 28 '21 at 21:23
  • 1
    "the goal is to figure out what OpenSSL is doing when it iterates through derived keys" - Just look at the source code of OpenSSL and you will see that. – mentallurg Mar 28 '21 at 21:37
  • OpenSSL isn’t a python library it’s a compiled C program. – B345T Mar 28 '21 at 21:48
  • 1
    I haven't said it is Python. I said it is open source. See the Git repository of OpenSSL. – mentallurg Mar 28 '21 at 23:20
  • Thank you for the link. I will look through it and OpenSSL.org thoroughly. – B345T Mar 29 '21 at 01:26
  • https://www.openssl.org/docs/man1.1.1/man3/EVP_BytesToKey.html.

    The key and IV is derived by concatenating D_1, D_2, etc until enough data is available for the key and IV. D_i is defined as:

    data is a buffer containing datal bytes which is used to derive the keying data. count is the iteration count to use. The derived key and IV will be written to key and iv respectively.

    D_i = HASH^count(D_(i-1) || data || salt)

    – B345T Mar 29 '21 at 01:37
  • By the way, that EVP_BytesToKey is not a PBKDF described in PKCS#5 (PBE) which defines PBKDF1 and PBKDF2; it's similar but proprietary to OpenSSL. – Maarten Bodewes Mar 29 '21 at 07:44
  • 1
    I propose we assume the OP wanted to write: For several months, I've been reverse-engineering openssl's Advanced Encryption Standard with cipher block chaining encryption, and rewriting it in python3. @mentallurg – fgrieu Mar 29 '21 at 09:51
  • You are correct @fgrieu. Thanks Maarten. Thanks for the pointer mentallurg. .. The PKCS#5 rfc doesn’t share anything useful about derived key iterations. Thanks for the discussion. You guys sound like cool dudes . – B345T Mar 29 '21 at 18:10
  • 1
    OP: specifying enc ... -iter 1 automatically uses PBKDF2 (which uses HMAC) and not EVP_BytesToKey (which uses a plain hash); see the man page on your system or on the web. @MaartenBodewes: in some cases including this one EVP_BytesToKey is almost PBKDF1; see my comparison at https://crypto.stackexchange.com/questions/3298/is-there-a-standard-for-openssl-interoperable-aes-encryption/#35614 . – dave_thompson_085 Mar 30 '21 at 01:04
  • Actually if you are paying attention, you'll notice that enc ... -iter 1 in 1.1.1 does NOT give the warning about 'deprecated key derivation used' which should clue you in that PBKDF2 was used (and EVP_BytesTokey was not) – dave_thompson_085 Mar 30 '21 at 02:06
  • OK, interesting that they didn't use a more explicit switch for that, very confusing. It's not PBKDF1 though, which the author put in the question / comments, so I'm still happy that I brought it up, even though I was wrong. B345T, you can lookup the algorithm descriptions of PBKDF1/2 in PKCS#5 RFC. – Maarten Bodewes Mar 30 '21 at 08:19
  • 1
    Thank you Maarten and @dave_thompson_085 for the input. You are correct. Today I discovered and confirmed that OpenSSL uses a default 10,000 iterations when -pbkdf2 is specified. Also I found the comparison link that you provided to be extremely informative. – B345T Mar 31 '21 at 03:17
  • Looking back at this I think this would be more appropriate on [so] but I guess it doesn't do any harm where it is. Please do not use this as an example of what is on topic on [crypto.se] though. – Maarten Bodewes Dec 23 '22 at 15:25

1 Answers1

0

Thank you all for all of the excellent help. I am happy to say that with your help I have found the answer to my initial question and more. The code below accepts a [string] password, [hex string] salt, [int] key size, and [int] iteration count, and returns a tuple containing the correct key and IV for PBKDF2. OpenSSL PBKDF2 uses 10,000 iterations by default. Leave the salt in the command as double quotes if there is no salt.

Proof:

printf "Today is a good day!" | openssl enc -aes-192-cbc -md sha512 -pbkdf2 -S 436172616d656c73 -k "Sparky" -p

results in:

salt=436172616D656C73

key=37A12300EF8D3442B266B41172154DFE0836803C4257AFB6

iv=E2FC1F1DF27AB4A6F303EDD6537EEE53

The same salt, key, and IV can be obtained by using the code and class below:

from pbkdf2 import kdf

key, iv = kdf.pbkdf2(“Sparky”,”436172616D656C73”,192,10000)

results in:

key=37A12300EF8D3442B266B41172154DFE0836803C4257AFB6 iv=E2FC1F1DF27AB4A6F303EDD6537EEE53

“Sparky” is the password “436172616D656C73” is the salt 192 is the AES-CBC key size 10,000 is the number of hmac iterations (pbkdf2)

And here is the Python3 code (pbkdf2.py).

Import hashlib

import hmac

class kdf:

def pbkdf2(passwd, salt, size, iterations):
dictionary = {128:((0,32),(32,64)), 192:((0,48),(48,80)), 256:((0,64),(64,96))}

k = b = hmac.new(passwd.encode("utf-8"), bytes.fromhex(salt) + b'\x00\x00\x00\x01' if len(salt) > 0 else b'\x00\x00\x00\x01', hashlib.sha512).digest()

for i in range(2, iterations + 1):
    b = hmac.new(passwd.encode("utf-8"), b, hashlib.sha512).digest()
    k = bytes(i ^ j for i, j in zip(k, b))


key = k.hex()[dictionary.get(size)[0][0]:dictionary.get(size)[0][1]]
_iv = k.hex()[dictionary.get(size)[1][0]:dictionary.get(size)[1][1]]

return key,_iv

B345T
  • 1
  • 1