48

A large amount of files were encrypted by

    openssl enc -aes-256-cbc -pass pass:MYPASSWORD

Openssl should derive key+IV from passphrase. I'd like to know key+IV equivalent of that MYPASSWORD. Is that possible?

I know MYPASSWORD. I could decrypt and then re-encrypt with new known key+IV with:

    openssl enc -d -aes-256-cbc -pass pass:MYPASSWORD
    openssl enc -aes-256-cbc -K MYKEY -IV MYIV

But the problem is that the amount of data is quite large.

1

5 Answers 5

108

Usage of the openssl enc command-line option is described there. Below, I will answer your question, but don't forget to have a look at the last part of my text, where I take a look at what happens under the hood. It is instructive.


OpenSSL uses a salted key derivation algorithm. The salt is a piece of random bytes generated when encrypting, stored in the file header; upon decryption, the salt is retrieved from the header, and the key and IV are re-computed from the provided password and salt.

At the command-line, you can use the -P option (uppercase P) to print the salt, key and IV, and then exit. You can also use the -p (lowercase P) to print the salt, key and IV, and then proceed with the encryption. First try this:

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -P

If you run this command several times, you will notice each invocation returns different values ! That's because, in the absence of the -d flag, openssl enc does encryption and generates a random salt each time. Since the salt varies, so do the key and IV. Thus, the -P flag is not very useful when encrypting; the -p flag, however, can be used. Let's try again; this time, we have the file foo_clear which we want to encrypt into foo_enc. Let's run this:

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -p -in foo_clear -out foo_enc

This command will encrypt the file (thus creating foo_enc) and print out something like this:

salt=A68D6E406A087F05
key=E7C8836AD32C688444E3928F69F046715F8B33AF2E52A6E67A626B586DE8024E
iv=B9F128D827203729BE52A834CC0890B7

These values are the salt, key and IV actually used to encrypt the file.

If I want to get them back afterwards, I can use the -P flag in conjunction with the -d flag:

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -d -P -in foo_enc

which will print the same salt, key and IV as above, every time. How so? That's because this time we are decrypting, so the header of foo_enc is read, and the salt retrieved. For a given salt value, derivation of the password into key and IV is deterministic.

Moreover, this key-and-IV retrieval is fast, even if the file is very long, because the -P flag prevents actual decryption; it reads the header, but stops there.

Alternatively, you can specify the salt value with the -S flag, or de-activate the salt altogether with -nosalt. Unsalted encryption is not recommended at all because it may allow speeding up password cracking with pre-computed tables (the same password always yields the same key and IV). If you provide the salt value, then you become responsible for generating proper salts, i.e. trying to make them as unique as possible (in practice, you have to produce them randomly). It is preferable to let openssl handle that, since there is ample room for silent failures ("silent" meaning "weak and crackable, but the code still works so you do not detect the problem during your tests").


The encryption format used by OpenSSL is non-standard: it is "what OpenSSL does", and if all versions of OpenSSL tend to agree with each other, there is still no reference document which describes this format except OpenSSL source code. The header format is rather simple:

magic value (8 bytes): the bytes 53 61 6c 74 65 64 5f 5f
salt value (8 bytes)

Hence a fixed 16-byte header, beginning with the ASCII encoding of the string Salted__, followed by the salt itself. That's all! No indication of the encryption algorithm; you are supposed to track that yourself.

The process by which the password and salt are turned into the key and IV is un-documented, but the source code shows that it calls the OpenSSL-specific EVP_BytesToKey() function, which uses a custom key derivation function (KDF) with some repeated hashing. This is a non-standard and not-well vetted construct (!) which relies on the MD5 hash function of dubious reputation (!!); that function can be changed on the command-line with the undocumented -md flag (!!!); the "iteration count" is set by the enc command to 1 and cannot be changed (!!!!). This means that the first 16 bytes of the key will be equal to MD5(password||salt), and that's it.

This is quite weak! Anybody who knows how to write code on a PC can try to crack such a scheme and will be able to "try" several dozens of millions of potential passwords per second (hundreds of millions will be achievable with a GPU). If you use openssl enc, make sure your password has very high entropy! (i.e. higher than usually recommended; aim for 80 bits, at least). Or, preferably, don't use it at all; instead, go for something more robust (GnuPG, when doing symmetric encryption for a password, uses a stronger KDF with many iterations of the underlying hash function).

12
  • This is the best insight to how openssl aes-256-cbc works!
    – Xample
    Commented Jan 22, 2015 at 23:25
  • 1
    Checking in at 2016. Are the keys generated by openssl's default KDF still weak, or has it been fixed? Commented Dec 30, 2015 at 19:48
  • 2
    @DesmondLee, it looks like openssl enc is still using a very weak KDF: github.com/openssl/openssl/blob/master/apps/enc.c#L453 -- for what it's worth, I doubt they'll change this.
    – sarnold
    Commented Feb 4, 2016 at 2:42
  • 2
    @neubert See my answer. The first 16 bytes are actually derived using PBKDF1 as defined in PKCS#5 v1.5. The next 16 bytes would be MD5(PBKDF1(PASSWORD, SALT) || PASSWORD || SALT) and the IV would be MD5(MD5(PBKDF1(PASSWORD, SALT) || PASSWORD || SALT) || PASSWORD || SALT)
    – Null
    Commented Mar 24, 2016 at 15:31
  • 4
    @DesmondLee FWIW OpenSSL 1.1.0 released 2016-08 changes the default -md to sha256; and contrary to our beloved bear it was actually documented since 1.0.0 released 2010-03. But the iteration count still remains fixed at 1, which is the real disaster. Commented Dec 24, 2016 at 22:29
9

Have you tried the -P option?

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -P

salt=28C5AD65428E4FD2
key=215202893B8CFEE68E733F69C55AE4C7BED7B2A06533774F3EA0894880E585E6
iv =186DE986FC69F8E47ED692B24D940BED
6

I'd like to know key+IV equivalent of that MYPASSWORD

You just need to replicate the key derivation function in EVP_BytesToKey, which is fairly simple.

If you don't specify -md (or specify -md md5) then the KDF used is MD5 based but the first 16 bytes are derived using PKCS#5 PBKDF1 with an iteration count of 1. This is then extended using the key derivation algorithm specified in EVP_BytesToKey to meet the size requirements of the key and IV

If you do specify the underlying hash function using -md and choose a hash function other than MD5 (eg -md sha256), then OpenSSL will only KDF specified in EVP_BytesToKey (and not PBKDF1).

Implementation in Python 3 (needs passlib):

import hashlib, binascii
from passlib.utils.pbkdf2 import pbkdf1

def hasher(algo, data):
    hashes = {'md5': hashlib.md5, 'sha256': hashlib.sha256,
    'sha512': hashlib.sha512}
    h = hashes[algo]()
    h.update(data)

    return h.digest()

# pwd and salt must be bytes objects
def openssl_kdf(algo, pwd, salt, key_size, iv_size):
    if algo == 'md5':
        temp = pbkdf1(pwd, salt, 1, 16, 'md5')
    else:
        temp = b''

    fd = temp    
    while len(fd) < key_size + iv_size:
        temp = hasher(algo, temp + pwd + salt)
        fd += temp

    key = fd[0:key_size]
    iv = fd[key_size:key_size+iv_size]

    print('salt=' + binascii.hexlify(salt).decode('ascii').upper())
    print('key=' + binascii.hexlify(key).decode('ascii').upper())
    print('iv=' + binascii.hexlify(iv).decode('ascii').upper())

    return key, iv

Examples (tested with OpenSSL 0.9.8 and 1.0.1):

openssl_kdf('md5', b'test', b'\xF6\x81\x8C\xAE\x13\x18\x72\xBD', 32, 16)
# generates the same output as:
openssl enc -aes-256-cbc -P -pass pass:test -S F6818CAE131872BD 

openssl_kdf('sha256', b'test', b'\xF6\x81\x8C\xAE\x13\x18\x72\xBD', 32, 16)
# generates the same output as:
openssl enc -aes-256-cbc -P -pass pass:test -S F6818CAE131872BD -md SHA256
0
2

In recent openssl (also tested with OpenSSL 1.1.0h 27 Mar 2018) it seems easy and straightforward to verify:

    openssl version
OpenSSL 1.0.2k-fips  26 Jan 2017

    echo -n salt1234 | od -A n -t x1 | perl -lpe's,\s+,,g'
73616c7431323334

    openssl enc -e -aes-128-cbc -pass pass:123 -S 73616c7431323334 -P -md sha256
salt=73616C7431323334
key=5A7C52236BAEAE1A92A6B2D1E50C43ED
iv =9F629A34588A4006FE1C7E8FC664B5EC

    echo -n 123salt1234 | openssl dgst -sha256 -binary | hexdump -Cv
00000000  5a 7c 52 23 6b ae ae 1a  92 a6 b2 d1 e5 0c 43 ed  |Z|R#k.........C.|
00000010  9f 62 9a 34 58 8a 40 06  fe 1c 7e 8f c6 64 b5 ec  |.b.4X.@...~..d..|
00000020

If you look closely, the -P output matches the hexdump output.

Conclusion: don't use enc for anything more than study and exploration, it is just a nice toy (in case you didn't know already).

1
  • Yes (since 1.1.0 release, or any earlier version if you specify -md sha256) for AES128 which needs 128+128 bits key material. See my comment to the bear. (Also des3=192+64.) But try AES192 or AES256, or analogous Camellia. Commented Aug 16, 2019 at 2:48
1

Key derivation for openssl enc for

  • versions prior to v1.1.0
  • versions 1.1.0 or later with -md md5

can be summed up as follows:

D1=md5(passwordbytes + salt)
D2=md5(D1 + passwordbytes + salt)
D3=md5(D2 + passwordbytes + salt)

key=D1+D2
iv=D3

As an example, we can use the following openssl command to create some ciphertext using openssl enc with -md md5:

echo -n 'Hello World!' | openssl enc -e -aes-256-cbc -md md5 -a

When prompted for the password, I entered the password, 'p4$$w0rd'. The ciphertext output produced by the command was:

U2FsdGVkX1+Fqn+TvPF3Yk0Sjlcnd0YhML0EVDa+6Zg=

Now, we can use openssl with the -P option to view the salt, key and iv that openssl used to generate the ciphertext above:

echo -n 'U2FsdGVkX1+Fqn+TvPF3Yk0Sjlcnd0YhML0EVDa+6Zg=' | base64 -d | openssl enc -d -aes-256-cbc -md md5 -P

This produces:

salt=85AA7F93BCF17762
key=EAE92A58AD052919A1B82511F31633B600168B24620728F0D383A5E5FE7C29A0
iv =8EFA1B88DB36BC9773184F482C07B217

The following python script implements the key derivation process described above:

import base64
import hashlib

#inputs
openssloutputb64='U2FsdGVkX1+Fqn+TvPF3Yk0Sjlcnd0YhML0EVDa+6Zg='
password='p4$$w0rd'

#convert inputs to bytes
openssloutputbytes=base64.b64decode(openssloutputb64)
passwordbytes=password.encode('utf-8')
salt=openssloutputbytes[8:16]   #salt is bytes 8-15 of the ciphertext

#key derivation
D1=hashlib.md5(passwordbytes + salt).digest()
D2=hashlib.md5(D1 + passwordbytes + salt).digest()
D3=hashlib.md5(D2 + passwordbytes + salt).digest()
key=D1+D2
iv=D3

print('password:', password)
print('salt:', salt.hex())
print ('key:', key.hex())
print ('iv:', iv.hex())

As expected, it produces the same results as the openssl command with the -P option:

password: p4$$w0rd
salt: 85aa7f93bcf17762
key: eae92a58ad052919a1b82511f31633b600168b24620728f0d383a5e5fe7c29a0
iv: 8efa1b88db36bc9773184f482c07b217

For more information, see dave_thompson_085's answer here, and see Thomas Pornin's answer above.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .