0

I'm trying to decrypt an encrypted ECDSA_secp256k1 private key generated using the OpenSSL CLI command openssl ecparam -genkey -name secp256k1 | openssl ec -aes-128-cbc -out ecdsa_priv.pem but I want to do it using pure Javascript. I tried using node-forge, asn1js and pkijs but without success.

Example encrypted key:

Note: I believe the hexValue is the IV (initialization vector) used to generate the key

-----BEGIN EC PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC, `hexValue`

`encrypted key`
-----END EC PRIVATE KEY-----

Basically the steps that I'm trying are:

  1. Extract the decryption algorithm - AES-128-CBC from the example and the IV

  2. Applying the PBKDF2 function - i.e: pbkdf2Sync() function from crypto with the password that I know that the given key is being encrypted with in the first place, "" empty string as a salt, 1 as iterations and "sha1" as digest because I've read that these are the default values that OpenSSL uses under the hood. And I want the keylen of the derivedKey to be 16.

  3. Then create a custom decipher with crypto.createDecipheriv('aes-128-cbc', derivedKey, iv);

  4. Then call await Buffer.concat([decipher.update(pemHex), decipher.final()]));

  5. And get this error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length

Using node-forge, I got the farthest as I got actually a decrypted private key but it is different than the one that I want to get (I know what should be the decrypted private key in the end). As I dug deeper I saw that generating a derivedKey with the same arguments with both node-forge and crypto was yielding different keys. As I read PBKDF2 should be deterministic, so I assume node-forge implementation differs somehow and thats why the private key that I'm comparing it to also differs

Can someone please give me some guidance on how to achieve this?

1 Answer 1

0

It's not PBKDF2, it's not SHA1, and it's not unsalted. OpenSSL's 'legacy' or 'traditional' privatekey (PEM) encryption uses OpenSSL's EVP_BytesToKey which is based on (in this case equal to) PBKDF1, with MD5 n=1 and using the DEK-info value both as salt (for 8 bytes) and as IV -- this differs from (old) openssl enc which is provided as (deprecated) create{Cipher,Decipher} in nodejs crypto.

The following code does the decryption; since you give no clue what you want to do with the result, I just write it back out in unencrypted PEM format:

const crypto = require('crypto');
const fs = require('fs');

let file = 'se270720.eky', pw = 'password';
var lines = fs.readFileSync(file,'ascii').split('\n');
if (lines[0].startsWith('-----BEGIN ') && lines[0].endsWith('-----')
&& lines[lines.length-1] == ''
&& lines[lines.length-2] == lines[0].replace('BEGIN','END')
&& lines[1].toLowerCase() == 'proc-type: 4,encrypted'
&& lines[2].toLowerCase().startsWith('dek-info: aes-128-cbc,')
&& lines[3] == '' ) {
  var saltiv = Buffer.from(lines[2].substring(22),'hex');
  var key = crypto.createHash('md5').update(pw).update(saltiv.slice(0,8)).digest();
  var decr = crypto.createDecipheriv('aes-128-cbc',key,saltiv);
  var ctext = Buffer.from(lines.slice(4,lines.length-2).join(''),'base64');
  var plain = Buffer.concat([decr.update(ctext),decr.final()]);
  var newbody = plain.toString('base64').replaceAll(/.{64}/g,'$&\n');
  console.log([lines[0],newbody,lines[lines.length-2],''].join('\n'));
}

As in your question this uses nodejs crypto which is of course not pure Javascript. Pure JS would vary quite a bit depending on what library(s) you use.

1
  • Thanks a lot! I needed to tweak a bit here and there, but the essential part did the job and I'm very grateful! Thanks also for all the clarifications about the process!
    – petreze
    Jun 13, 2023 at 7:17

You must log in to answer this question.

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