I have a file that has been encrypted using openssl using the following command:
openssl enc -in data -out encrypted -e -aes256 -k myverystrongpassword
Where data is the original file and encrypted is the encrypted file.
I tried various ways using crypto library but nothing seems to work. I understand that the password needs to be converted into a key so maybe I am doing something wrong there. Looked all over for a solution but nothing seems to work.
The posted OpenSSL statement uses a key derivation function EVP_BytesToKey()
to derive a 32 bytes key and a 16 bytes IV from the password in combination with a random 8 bytes salt.
The ciphertext corresponds to the concatenation of the ASCII encoding of Salted__
, followed by the salt and finally by the actual ciphertext.
As you already know according to your comment, EVP_BytesToKey()
uses a digest for which OpenSSL applied MD5 by default in earlier versions and SHA-256 as of version v1.1.0 (the default value can be overridden in the OpenSSL statement with the -md
option).
Decryption is possible e.g. with CryptoJS: Due to its OpenSSL compatibility (s. sec. Interoperability) CryptoJS has a built-in implementation of an accessible EVP_BytesToKey()
function and additionally allows to explicitly set the digest in the internal EVP_BytesToKey()
call during key derivation. This makes it possible to decrypt encryptions that used SHA-256 or MD5 in key derivation.
The following data is the Base64 encoding of a ciphertext generated with the posted OpenSSL statement. The plaintext used was The quick brown fox jumps over the lazy dog. The OpenSSL version applied is v1.1.1i (i.e. SHA-256 is implicitly used in the key derivation):
U2FsdGVkX19W4wmC9dD35X4J66cSvaRaIQpvjDKHrLF9+qYg5VTo5urvExHLXhwf/bE8FXJTQZmKN8ITMJVdqQ==
This ciphertext can be successfully decrypted using the following CryptoJS implementation:
const password = 'myverystrongpassword';
const saltCiphertextB64 = 'U2FsdGVkX19W4wmC9dD35X4J66cSvaRaIQpvjDKHrLF9+qYg5VTo5urvExHLXhwf/bE8FXJTQZmKN8ITMJVdqQ==';
CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create(); // default: MD5
const decryptedData = CryptoJS.AES.decrypt(saltCiphertextB64, password);
console.log(decryptedData.toString(CryptoJS.enc.Utf8)); // The quick brown fox jumps over the lazy dog
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Note that the digest in the code must be explicitly specified as SHA-256 since OpenSSL v1.1.1i was used for encryption.
If the encryption was done with an OpenSSL version that uses MD5, the digest in the code must be modified accordingly.
Edit: As noted in the comment, the crypto functions createCipher()
/createDecipher()
also use EVP_BytesToKey()
as key derivation.
However, the following should be noted:
Salted__|<salt>|<cipherext>
) during encryption and separation during decryption would have to be implemented additionally. createCipher()
/createDecipher()
then has to be passed the concatenation of passphrase and salt.A more robust approach to decrypt encryptions (with arbitrary digests in key derivation) using the crypto module is to apply createCipheriv()
/createDecipheriv()
and a port of the required functionality of EVP_BytesToKey()
to derive key and IV (various implementations can be found on the net).
Security: EVP_BytesToKey()
is deemed to be a vulnerability these days. This is worsened by a low iteration count (like 1, which is used by OpenSSL), a broken digest (like MD5) or a missing salt (as is the default for crypto). Ultimately, this is why createCipher()
/createDecipher()
are deprecated. Instead of EVP_BytesToKey()
, a more reliable key derivation function such as PBKDF2 or the more modern scrypt or Argon2 should be used.