Generate RSA key pair using WebCrypto API and protect it with passphrase
Asked Answered
U

2

6

title says it all. I was wondering how do I generate RSA key pair using WebCrypto API and how do I secure it with a passphrase so I can store it in a database.

Unipersonal answered 16/7, 2016 at 16:47 Comment(4)
with 'database', you mean store in the IndexedDB ?Henshaw
@Henshaw no I mean any database. I was wondering if there is a functionality similar to the one in PHP OpenSSL. I want to recreate this piece of code in WebCrypto API: openssl_pkey_export($key_pair, $encrypted_private_key, $passphrase);Unipersonal
@Henshaw but I think i maybe figured out the solution, I started to write own library on top of the API, will keep you updated.Unipersonal
basicaly I want to secure the key so it can be stored in unsecure environment and I was looking if there is any recommended solution for encrypting private key, if not I will find another way.Unipersonal
H
12

You can generate an RSA key pair with WebCrypto and export it as jwk (Json Web Key), pkcs#8 (private) or spki (public). See SubtleCrypto.exportKey() and the example code bellow

To export the key to an external system in a protected way you could use an standard like:

  • PKCS#8: The PKCS#8 private key format defined at IETF Public Key-Cryptographic Standard Encryption #8. allow encryption with a passphrase, but WebCryptography exportKey does not support it. It provides PrivateKeyInfo

  • PKCS#12: PKCS#12 is a keystore exchange format. It can contain private keys, certificates with the public key and the certification chain. The content is 3DES encrypted with a passphrase. Files are usually found with extension .pfx or .p12

Unfortunately WebCrypto does not support exporting in a common format with encryption such as PKCS#8 - encrypted or PKCS#12. You could export the keys in one of these formats using a third party library like forge

Example code

WebCrypto RSASSA-PKCS1-v1_5 - generateKey

window.crypto.subtle.generateKey(
    {
        name: "RSASSA-PKCS1-v1_5",
        modulusLength: 2048, //can be 1024, 2048, or 4096
        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
        hash: {name: "SHA-256"}, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
    },
    true, //whether the key is extractable (i.e. can be used in exportKey)
    ["sign", "verify"] //can be any combination of "sign" and "verify"
)
.then(function(key){
    //returns a keypair object
    console.log(key);
    console.log(key.publicKey);
    console.log(key.privateKey);
})
.catch(function(err){
    console.error(err);
});

WebCrypto RSASSA-PKCS1-v1_5 - exportKey

window.crypto.subtle.exportKey(
    "pkcs8", //can be "jwk" (public or private), "spki" (public only), or "pkcs8" (private only)
    privateKey //can be a publicKey or privateKey, as long as extractable was true
)
.then(function(keydata){
    //returns the exported key data
    console.log(keydata);
})
.catch(function(err){
    console.error(err);
});

Forge -PKCS#8

//needed: wrap webcrypto pkcs#8 to forge privateKey (see doc)

// encrypts a PrivateKeyInfo and outputs an EncryptedPrivateKeyInfo
var encryptedPrivateKeyInfo = pki.encryptPrivateKeyInfo(
  privateKeyInfo, 'password', {
    algorithm: 'aes256', // 'aes128', 'aes192', 'aes256', '3des'
  });

// converts an EncryptedPrivateKeyInfo to PEM var pem = pki.encryptedPrivateKeyToPem(encryptedPrivateKeyInfo);

Forge - PKCS#12

//needed: wrap webcrypto pkcs#8 to forge privateKey (see doc)

// generate a p12 that can be imported by Chrome/Firefox
// (requires the use of Triple DES instead of AES)
var p12Asn1 = forge.pkcs12.toPkcs12Asn1(privateKey, certChain, password,  {algorithm: '3des'});

// base64-encode p12
var p12Der = forge.asn1.toDer(p12Asn1).getBytes();
var p12b64 = forge.util.encode64(p12Der);
Henshaw answered 17/7, 2016 at 20:12 Comment(10)
Pedro thank you very much for your detailed answer. It is very appreciated. You saved me a lot of time.Unipersonal
Pedro, I was wondering if there is a way to encrypt private key without the use of Forge lib. WebCrypto wrapKey method supports pkcs8 keys.Unipersonal
I have reviewed the webcrypto specification and seems the wrapKey allow to encrypt the private key with AES or RSA-OAP. The wrapping key has to be previously imported. So I think wrapKey can be used instead of forge. I will include it in the answerHenshaw
good, there is one more thing, I've probably overlooked something or did something wrong but when I wrapped key using WebCrypto I couldn't unwrap the key using OpenSSL. I did this:Unipersonal
derived key from passphrase using PBKDF2 with 300 000 iterations imported it to AES-GCM and wrapped private key as pkcs8, but OpenSSL could not handle it... which I was not a lot surprised actually. I couldn't find any specification how many PBKDF2 iterations OpenSSL uses or what the standard is. This is the only piece of info that I found: tools.ietf.org/html/rfc5208#section-6Unipersonal
I also noticed that every base64 private key both encrypted and not encrypted starts with -----BEGIN PRIVATE KEY-----MIIEvg but key wrapped using WebCrypto starts with XUt1qAY so maybe ASN.1 syntax is not automatically defined in WebCrypto. I really don't know.Unipersonal
Peter, I think I can not help you in this point. I have never used OpenSSL for this purpose. I suggest to post a new questionHenshaw
never mind, thanks a lot for help. btw I actually meant encrypting the key to this format -----BEGIN ENCRYPTED PRIVATE KEY----- then it can be used by OpenSSL and many othersUnipersonal
Hello Pedro, so I wrote a crypto library built on top of WebCrypto API as I said. It allows you to encrypt private key into PKCS #8 RFC5208 EncryptedPrivateKeyInfo format which is compatible with OpenSSL and there is no need to use any third party library like Forge. It's also very lightweight.Unipersonal
Good job, Peter, I will take a look.Henshaw
U
3

Download this little crypto library called OpenCrypto which allows you to encrypt private key using your passphrase into PKCS #8 RFC 5208 EncryptedPrivateKeyInfo format. Library uses only plain JavaScript, WebCrypto API and also Promises.

Here is the link for the library: https://github.com/PeterBielak/OpenCrypto The library is free for commercial use under MIT license. Enjoy! ;)

and here is a simple example:

var crypt = new OpenCrypto();

crypt.getKeyPair().then(function(keyPair) {
     crypt.encryptPrivateKey(keyPair.privateKey,'securepassword').then(function(encryptedPrivateKey) {
        // This PEM Encrypted Private Key is fully compatiable with OpenSSL
        console.log(encryptedPrivateKey);
    });

});
Unipersonal answered 1/9, 2016 at 19:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.