How to use the Web Crypto API to decrypt a file created with OpenSSL?

7.6k views Asked by At

I am trying to decrypt a file that was created using the OpenSSL command-line interface. This file was created with:

openssl aes-256-cbc -a -in file.txt -out file_encrypted.txt

And can be decrypted with:

openssl aes-256-cbc -d -a -in file_encrypted.txt

By using the -p flag I can retrieve the actual value, salt and IV which will be required by the WebCrypto API:

> openssl aes-256-cbc -d -a -p -in file_encrypted.txt
salt=F57F1CC0CD384326
key=0E971326890959386F1CFB91F185CFE109203DCEBC81DCAD4EE642F34C538E5B
iv=A884549B66400EB198879F8A09148D4E
secret text

My current attempt looks like this:

function getKey (password) {
    return crypto.subtle.digest({name: "SHA-256"}, convertStringToArrayBufferView(password)).then(function(result){
        return crypto.subtle.importKey("raw", result, {name: "AES-CBC"}, false, ["encrypt", "decrypt"]);
    });
}

function decrypt(key, data, iv) {
    return crypto.subtle.decrypt({ name: "AES-CBC", iv: iv }, key, data).then(function(result){
        var decrypted_data = new Uint8Array(result);
        return convertArrayBufferViewtoString(decrypted_data);
    }, fail);
}

var encrypted = Uint8Array.from('0E971326890959386F1CFB91F185CFE109203DCEBC81DCAD4EE642F34C538E5B'.match(/\w\w/g));
var IV = Uint8Array.from('A884549B66400EB198879F8A09148D4E'.match(/\w\w/g));

getKey(prompt('Enter decryption password:')).then(function (key) {
    decrypt(key, encrypted, IV).then(result => {
        console.log(`password: ${result}`)
    });
}, fail);

(array-to-buffer methods ommited for brevity - taken from http://qnimate.com/passphrase-based-encryption-using-web-cryptography-api/)

This fails with an unspecified DOMException though and I have no idea what to do next.

3

There are 3 answers

0
pedrofb On

OpenSSL applies a salted key derivation algorithm to your password using some random bytes generated when encrypting and stored in the header of the encrypted file.

In this post is very well explained

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

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.

Hence a fixed 16-byte header, beginning with the ASCII encoding of the string "Salted__", followed by the salt itself.

To make your code works is needed:

  • Load the key generated by OpenSSL (or derive the key from password using the provided salt with the openssl algorithm. The derivation algorithm is undocumented in the openssl encryption page, but in this post is said that is propietary, so it is not available in webcrypto)

  • decode from HEX to ArrayBuffer using hex2a and convertStringToArrayBufferView

    var IV = convertStringToArrayBufferView (hex2a ('A884549B66400EB198879F8A09148D4E'));

  • Load the encrypted file: decode from base64 (you used -a option) and remove the first 16 bytes of the salt

This a simplified javascript example with data generated with the same openssl command

openssl aes-256-cbc -d -a -p -in file_encrypted.txt
enter aes-256-cbc decryption password:
salt=886DBE2C626D6112
key=0DA435C43BE722BB5BF09912E11E3E25BE826C35A674EC4284CD1C49AFBCC78E
iv =7F9608BF748309A2C7DAA63600AB3825
this is the secret value of the fiile

Javascript code

//The content of file_encrypted.txt. It is encoded in base64
var opensslEncryptedData = atob('U2FsdGVkX1+Ibb4sYm1hEp/MYnmmcteeebZ1jdQ8GhzaYlrgDfHFfirVmaR3Yor5C9th02S2wLptpJC6IYKiCg==');
//Encrypted data removing salt and converted to arraybuffer
var encryptedData = convertStringToArrayBufferView(opensslEncryptedData.substr(16,opensslEncryptedData.length););

//key and IV. salt would be needed to derive key from password
var IV = convertStringToArrayBufferView (hex2a ('7F9608BF748309A2C7DAA63600AB3825'));
var key = convertStringToArrayBufferView (hex2a ('0DA435C43BE722BB5BF09912E11E3E25BE826C35A674EC4284CD1C49AFBCC78E'));
//var salt = convertStringToArrayBufferView (hex2a ('886DBE2C626D6112'));

crypto.subtle.importKey("raw", key, {name: "AES-CBC"}, false, ["encrypt", "decrypt"]). then (function (cryptokey){

    return crypto.subtle.decrypt({ name: "AES-CBC", iv: IV }, cryptokey, encryptedData).then(function(result){
        var decrypted_data = new Uint8Array(result);
        var res =  convertArrayBufferViewtoString(decrypted_data);
        console.log(res);
    }).catch (function (err){
        console.log(err);
    }); 

}).catch (function (err){
    console.log(err);
});    

Utility functions

function hex2a(hexx) {
    var hex = hexx.toString();//force conversion
    var str = '';
    for (var i = 0; i < hex.length; i += 2)
        str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
    return str;
}

function convertStringToArrayBufferView(str){
    var bytes = new Uint8Array(str.length);
    for (var iii = 0; iii < str.length; iii++) {
        bytes[iii] = str.charCodeAt(iii);
    }

    return bytes;
}

function convertArrayBufferViewtoString(buffer){
    var str = "";
    for (var iii = 0; iii < buffer.byteLength; iii++) {
        str += String.fromCharCode(buffer[iii]);
    }

    return str;
}
0
Yury Strozhevsky On

In fact working with CMS EnvelopedData (encrypted messages) is not a trivial task. And in order to make it easier to work with such complex data always better would be to use already made and tested libraries.

At the moment only one such lib is PKIjs (and new, ES6 version of PKIjs is here). There are a lot of live examples here. For your specific question there are two examples:

  1. How To Encrypt CMS via certificate
  2. How To Encrypt CMS via password

Hope it will help you to use WebCrypto in a correct way, as it should be :)

0
Etienne Martin On

I've created a small library to do just that. Thanks to @pedrofb for his answer.

Embed WebCrypto.js (Minified) in your document.

Use it like this:

// Initialize the library
initWebCrypto();

var encrypted = "wl2v/1oY7NqV58jpSGkNKmKNu6cdNDz7QCSmKk61k9gyG2Exxh3MxXf9kuSk/ESr6MGNdtQEAhSjHZ9b+Vc4Uw==";
var key = "6f0f1c6f0e56afd327ff07b7b63a2d8ae91ab0a2f0c8cd6889c0fc1d624ac1b8";
var iv = "92c9d2c07a9f2e0a0d20710270047ea2";

// Decrypt your stuff
WebCrypto.decrypt({
    data: encrypted,
    key: key,
    iv: iv,
    callback: function(response){
        if( !response.error ){
            console.log(atob(response.result));
        }else{
            console.error(response.error);
        }
    }
});

See https://github.com/etienne-martin/WebCrypto.swift/blob/master/www/index.html for more examples.

Source code: https://github.com/etienne-martin/WebCrypto.swift/blob/master/source.js

Hope this helps!