Lybero.net lybcrypt SDK documentation

Bertrand Wallrich

Summary

Introduction

lybcrypt is a cryptographic library for symmetric, asymmetric and asymmetric with quorum encryption/decryption, key management, key signature and web of trust.

Build & Test

Running tests is always a good idea:

    sudo npm install uglify-js -g
    npm i
    gulp

For testing in a browser, open your browser and go to :

https://localhost:4000:/test.html

Installation

To install the package for using in your application:

    cd <in the repository of your app>
    npm i <path of lybcrypt module>

and you can verify the module with :

    cd node_modules/lybcrypt
    npm test

and you can use in your app :

    import { KeyStore } from '@lybero/lybcrypt';

Used Algorithms

Transfer Properties and Constraints

All cryptographic operations are done in web browsers. Users' cryptographic keys are stored on the server.

All random values (AES keys for each file repository, asymmetric keys) are generated in the user's web browser. These operations are difficult to attack because they are distributed. If a server is hacked, this does not lead to the disclosure of any secrets.

User account creation

When a user creates an account, a keyStore must be generated in the web browser, associated to him. It will containt public/private key pair for an asymmetric Elgamal cryptographic algorithm. All the keyStore is sent to the central server. The private key is symmetrically encrypted with the AES algorithm and the secret administrator’s password before being sent to the server in the browser.

You can use whatever authentication scheme (including Oauth2, fido u2f double authentication, …) that you want. We provide functions to do pkpbf2 authentication. If the authentication is done successfully, then the private key must be retrieved from the server and it is decrypted in the browser, when necessary. The Perfect Forward Secrecy rule is respected on the server.

Getting started

What is a KeyStore ?

A keyStore from '@lybero/lybcrypt' is a realy simple object for using cryptographic primitives into a nodejs browser environement. It propose to avoid complexity and knowledges to developpers for end to end cryptography. It manage :

  • cryptographic keys (asymetric and symetric)
  • cryptographic functions
  • signature and trust (web of trust or PKI)
  • recovery using threshold cryptography

How to use KeyStores ?

In your application, you have users. Each user have an uniq userId (a string)

When a user logon, the server provide the keyStore to the client application (in the browser). The best way to do that is to store the keyStore into the user account in the database. Then, in the browser :


    import { KeyStore } from '@lybero/lybcrypt';

    var aliceKeyStore = new KeyStore(aliceId, getPublicKeyStoreFromStoragefunc );
    await aliceKeyStore.setContent( contentReceivedFromServerAtLogon );
    // --- The keyStore is ready to work.

For more details see KeyStore API Reference

How to create a keyStore ?

It is very simple.


    import { KeyStore } from '@lybero/lybcrypt';

    var aliceKeyStore = new KeyStore(aliceId, getPublicKeyStoreFromStoragefunc );
    await aliceKeyStore.create( "mySecretPassphrase" );
    // --- The keyStore is ready to work. It need to be saved now.
    let publicContent = await aliceKeyStore.getPublicContent();
    let content = await aliceKeyStore.getContent();
    
    sendToStorage( content, publicContent );

At this point, the keyStore is created with two keys :

  • a elGamal cryptographic key
  • a ECDSA signature key

Both corresponding private keys are protected by the passphrase.

You can add / import other keys after.

How to crypt ?

Too easy... Alice want to crypt data to bob.

On alice browser side...

    let dataToCipher = {
        when : "tomorrow",
        where : "at central Station",
        contract : "exchange secrets for a lot of dollars"
    }

    let encryptedStruct = await aliceKeyStore.cryptFor( bobId, dataToCipher );
    sendToStorage( encryptedStruct.getContent() )
    // r = true

How to decrypt ?

On bob browser side...

    import { KeyStore, ShareKeys } from '@lybero/lybcrypt';

    let r = await aliceKeyStore.sign( bobId, dataToCipher );

    // --- decrypt datas ----
    let clearData = await bobKeyStore.decrypt( shareKeys );
    console.log( clearData.when ); // tomorrow

How to sign bob's key ?

On Alice browser side...

    import { KeyStore, ShareKeys } from '@lybero/lybcrypt';

    let r = await aliceKeyStore.signUserKey( bobId );
    // r === true;

If the keyStore need alice passphrase for signing, it will ask the user for that.

KeyStore API Reference

This part describe the keyStore API reference. For Functions needed by KeyStore, see KeyStore Functions Reference

KeyStore Management API

 addQuorumMemberShares(quorumKeyId, memberId, secretSharesList)

add quorum member shares between administrators of the quorum.

parameter type Description
 quorumKeyId string The quorum keyId in the keyStore.
 memberId string The userId of the administrator which add the list of SharedKeys.
 secretSharesList array of strings  The list of SharedKeys

return a promise which return true if ok.

    return qKeyStore.addQuorumMemberShares("quorumKeyId", "aliceId", shareKeysList )
    .then ((keyId) => {
        console.log(keyId); // an Id...
        quorumKey = qKeyStore.getKey( keyId );
    })

When this operation is done for each administrator. administrators must do the sequence of :

getQuorumSharesForMe and computeMemberSecret and addQuorumPublicKey

At the end, The keyStore is modified and must be saved.

addQuorumPublicKey(quorumKeyId, memberId, pubKey)

Each administrator add the publicKey to the quorumKey. This key is signed by the administrator himself. When all administrators have done this part, the quorum key is ready for ciphering.

parameter type Description
 quorumKeyId string The quorum keyId in the keyStore.
 memberId string The userId of the administrator which add the list of SharedKeys.
 pubKey string  The quorum public key

This function return a promise which return true if ok, or false if an error occured.

At the end, The keyStore is modified and must be saved.

 computeMemberSecret(quorumUserId, quorumKeyId, secretSharesList)

This function compute the quorum member secret key, linked with the other members trought the secretSharesList.

parameter type Description
 quorumUserId string The user who has the quorum key in his keyStore.
 quorumKeyId string The quorum keyId in the keyStore.
 secretSharesList array of strings  The list of SharedKeys

Return a promise which return the publicKey of the quorum, signed by this keyStore.

      alice.computeMemberSecret("quorumId", quorumKeyId, secretSharesList)
        .then((pubKey) => {
            // send the pubKey to qKeyStore
        })

create ( [ passphrase ] )

return a promise for creation of the keyStore. It create a keyStore with the first signing ECDSA key and the first crypting elGamal key. Both private keys are protected with the passphrase.

It create a deviceKeyStore with its own elGamal key.

    var myKeyStore = new KeyStore(myId, getPublicKeyStoreFromStoragefunc );
    return myKeyStore.create("mySecretPassphrase")
    .then ((result) => {
        console.log(result); // true
    })

getPublicKeyStoreFromStoragefunc must be a function to retreive public keyStores.

At the end, The keyStore is modified and must be saved.

createQuorumKey(quorum, adminsList)

Create a quorum key in this keyStore.

parameter type Description
 quorum integer  The threshold.
 adminsList array of strings  The list of admins (by userId)

return a promise which return false if error or the keyId (string) created.

    var qKeyStore = new KeyStore("quorumKeyId", getPublicKeyStoreFromStoragefunc );
    var quorumKeyId = undefined;
    return qKeyStore.createQuorumKey(2, [ "aliceId", "bobId", "charlieId"] )
    .then ((keyId) => {
        console.log(keyId); // an Id...
        quorumKeyId = keyId;
        quorumKey = qKeyStore.getKey( keyId );
    })

Next, each adminstrator must do createQuorumPrivateKey

At the end, The keyStore is modified and must be saved.

createQuorumPrivateKey( quorumKey )

parameter type Description
 quorumKey QuorumKey the key created with createQuorumKey.

Create the quorum member key corresponding to the quorum key object

    var qKeyStore = new KeyStore("quorumId", getPublicKeyStoreFromStoragefunc );
    return alice.createQuorumPrivateKey( quorumKey )
    .then ((keyId) => {
        console.log(keyId); // an Id...
    })

At the end, The keyStore is modified and must be saved.

Next, each adminstrator must do initAndShare.

deleteDevice( deviceId )

Remove a device keyStore from your keyStore.

You can see all devices list of keys with getInfos function.

At the end, The keyStore is modified and must be saved.

initAndShare(keyId, adminsList)

parameter type Description
 keyId string  The quorum member keyId produced by createQuorumPrivateKey.
 adminsList array of strings  The list of admins (by userId)

initAndShare return a promise which return an array of ShareKeys to add to the quorum Key.

    var qKeyStore = new KeyStore("quorumId", getPublicKeyStoreFromStoragefunc );
    return alice.initAndShare(keyId, adminsList)
    .then ((shareKeysList) => {
        console.log(shareKeysList.length); // must be equal to adminList.length
    })

Next, the quorum keyStore must do addQuorumMemberShares for each shareKeysList produced by each administrator.

getContent()

Return a promise with the keyStore content as a JSON object. All private keys returned are protected by the given passphrase.

    return myKeyStore.getContent()
    .then ((content) => {
        console.log(content); // { ... huge :)
    })

 getInfos()

return a promise which return an object containing all information about the keyStore. All its keys (no private parts), devices, signatures, etc...

    return myKeyStore.getInfos()
    .then ((informations) => {
        console.log(informations); // { ... huge :)
    })

getPublicContent()

Return a promise with the keyStore public content as a JSON object. "public" means the keyStore without private keys.

    return myKeyStore.getPublicContent()
    .then ((publicContent) => {
        console.log(publicContent); // { ... huge :)
    })

getQuorumSharesForMe(quorumKeyId, memberId)

parameter type Description
 quorumKeyId string the key Id created with createQuorumKey.
 memberId string The userId of the administrator who want his list of SharedKeys.
    return qKeyStore.getQuorumSharesForMe( "quorumId", "aliceId" )
    .then ((shareKeysList) => {
        console.log(shareKeysList.length); // must be equal to adminList.length
    })

return the list of shareKeys for him (or false in case of error) (a crypt information from bobId, from aliceId (herself) and from charlieId).

isModified()

Return true if the keyStore has been modified.

    if ( myKeyStore.isModified() ) {
        ...
    }

preferedCryptKey = keyId

Set a prefered key for ciphering. In case of more than one available cipher key, you can choose the key to give to other people. otherwhise, they will crypt with all available keys.

You can see the keyStore list of keys with getInfos function.

At the end, The keyStore is modified and must be saved.

protectPrivateKeyWithPassphrase( keyId, passphrase )

Crypt the private key with an AES encryption.

parameter type Description
 keyId string the key Id to protect.
 passphrase string A passphrase.
    return myKeyStore.protectPrivateKeyWithPassphrase( "key1", "mySecretPassphrase" )
    .then ((ret) => {
        console.log(ret); // true
    })

At the end, The keyStore is modified and must be saved.

 setContent( content )

Set the keyStore content with a json object. Return a promise

    var loadedContentFromServer = httpGet... // get the content from your server
    var myKeyStore = new KeyStore(myId, getPublicKeyStoreFromStoragefunc );
    return myKeyStore.setContent( loadedContentFromServer )
    .then ((result) => {
        console.log(result); // true
    })

getPublicKeyStoreFromStoragefunc must be a function to retreive public keyStores.

setName( name )

Each keystore as an Id related to the userId (and given at the declaration). It is recommanded to add a human esaly readable name.

    var myKeyStore = new KeyStore(myId, getPublicKeyStoreFromStoragefunc );
    myKeyStore.setName("Alice")
    console.log(myKeyStore.name); // Alice

At the end, The keyStore is modified and must be saved.

 setPublicContent( content )

Set the keyStore content with a json object. It will contain only public keys Return a promise

    var loadedPublicContentFromServer = httpGet... // get the content from your server
    var aliceKeyStore = new KeyStore(aliceId, getPublicKeyStoreFromStoragefunc );
    return aliceKeyStoresetPublicContent( loadedPublicContentFromServer )
    .then ((result) => {
        console.log(result); // true
    })

stopListeners()

Stop listeners. You must stop listeners before a logout for example.

    myKeyStore.stopListeners()

trustLevel = level

Set the level of trust for this keyStore. The trustlevel of the keyStore must be the same or more restrictive than the global one.

    import { KeyStore, TRUSTLEVEL } from '@lybero/lybcrypt';

    var aliceKeyStore = new KeyStore(aliceId, getPublicKeyStoreFromStoragefunc );
    aliceKeyStore.trustlevel = TRUSTLEVEL.paranoid;

You can change the global trustlevel with globaltrustLevel

The available levels are :

Level Numeric Description
 TRUSTLEVEL.dontcare 0  Can crypt for everybody withoutknowledge about his public key.
 TRUSTLEVEL.medium 1  If you don't know anything about the recipient public key, ask the user for a confirmation to cipher for this person. In this case, the keystore propose to sign the recipient public key too.
 TRUSTLEVEL.paranoid 2  If the recipient public key isn't sign by you or somebody you trust, the cryptFor func will return an error

At the end, The keyStore is modified and must be saved.

Crypt and Decrypt API

acceptWanted(shareKeys, userId, quorumKeyId )

When a user as ask for cryptographic access to a SharedKeys crypted for you, you can accept (or refuse ) and build a new shareKey object crypted structure for the user.

acceptWanted return a promise

    var myKeyStore = new KeyStore(myId, getPublicKeyStoreFromStoragefunc );
    return myKeyStore.acceptWanted(sharedKeys, bobId )
    .then ((result) => {
        console.log(result); // true
    })

See addWanted for related informations.

Warning if you don't know the user Public key keyUserId, depending on the trustLevel, this function can return an error or ask for confirmation. Otherwhise, return a new SharedKeys object.

If you are a quorum member, fill the quorumKeyId parameter. In this case, this function can return :

output Description
 false An error occured, due to trutlevel for example
 true in case of a quorum accept wanted ( quorumKeyId parameter filled ), you have accepted, but the quorum is not reached. The shareKeys has been modified.
 ShareKeys object the quorum is raised, or this is a user to user acceptation. This is the new ShareKeys

addWanted(shareKeys, expire = 0)

ask to have cryptographic access to a SharedKeys expire is a delay in second. this function modify the shareKeys object.

addwanted return a promise

    var myKeyStore = new KeyStore(myId, getPublicKeyStoreFromStoragefunc );
    return myKeyStore.addWanted(sharedKeys)
    .then ((result) => {
        console.log(result); // true
    })

See acceptWanted for related informations.

cryptFor( userId, data, encryptedFormat = "base64" )

crypt data for a user with your keyStore. data can be :

* ArrayBuffer
* json object
* string

encryptedFormat can be :

* raw
* struct
* hex
* base64 (by default)

return an object SharedKeys or false if an error

    return myKeyStore.cryptFor( userId , "this is a secret" )
    .then ((content) => {
        console.log(content); // a ShareKeys
    })

decrypt( SharedKeys )

decipher an encrypted structure SharedKeys return a promise which return the clear object|ArrayBuffer|string or false if error.

decrypt use key in memory, or ask for privatekey if necessary, or use quorum keys. see decryptSessionKey

    return myKeyStore.decrypt( sharedKeys )
    .then ((content) => {
        console.log(content); // clear data (or false)
    })

decryptSessionKey( SharedKeys )

decipher an encrypted structure SharedKeys and install the AES symetric key in the keyStore.

return a promise which return true if ok false if error.

decryptSessionKey use key in memory, or ask for privatekey if necessary, or use quorum keys.

    return myKeyStore.decrypt( sharedKeys )
    .then ((content) => {
        console.log(content); // clear data (or false)
    })

Signature API

 dropSignature(userId, keyId, [myKeyId])

Remove a signature from your keyStore remove the user userId with the public key keyId from your signature list. you can specify your signature key in case of multiple signature keys.

 signUserKey(userId, keyId = undefined )

Sign the user KeyStore (his public key keyId) with your signature key.

    import { KeyStore } from '@lybero/lybcrypt';

    var aliceKeyStore = new KeyStore(aliceId, getPublicKeyStoreFromStoragefunc );


    await aliceKeyStore.signUserKey(bobId);
    await aliceKeyStore.verifySignature(bobId, keyId);

The keyStore will ask for passphrase if needed. return true of Ok (and the signature is added) or false if an error occured.

 verifySignature(userId, keyId)

Verify the signature for a user and a keyId. return a promise which return true if the key is signed by you or by another person you trust.

    import { KeyStore, TRUSTLEVEL } from '@lybero/lybcrypt';

    var aliceKeyStore = new KeyStore(aliceId, getPublicKeyStoreFromStoragefunc );
    let a = await aliceKeyStore.verifySignature(bobId, keyId);
    if ( a == true ) ...

The keyStore can ask for other public keyStores to find a trust path to the recipient userId.

KeyStore Functions Reference

The lybcrypt keyStore is designed to be as most separate as possible from the application. All privates keys never get out of the keyStore for example. This separation is designed to work like a "HSM" in the browser, and will work in a separated worker or in a extension.

But for some task, the keyStore need to ask for assets to the application. Some functions must be provided to the keyStore for that.

This part describe the keyStore Functions reference, and the minimal way to designe them.

Global scheme

This is the general view of exchanges between the keyStore and the application. global view

Some functions are global to all keyStores and some of them are in the scope of each keyStore.

Error function

You can provide the lybcrypt an arror function, called in case of error. This function is global and will be called by each keyStores.

    import { KeyStore, setErrorFunc } from '@lybero/lybcrypt';

    var myErrorFunc = ( message, obj ) => {
        // i can do what i want...
        console.error("message", obj);
        return false; // MANDATORY
    }

    setErrorFunc( myErrorFunc )

be default, without any settings, the error function do a console.error().

Warning The errorFunc given must return false.

Debug function

You can provide the lybcrypt a debug function. This function is global and will be called by each keyStores.

    import { KeyStore, setDebugFunc, startDebugOn, setDebug } from '@lybero/lybcrypt';

    var myDebugFunc = ( message, obj ) => {
        // i can do what i want...
        console.log("message", obj);
    }

    setDebugFunc( myDebugFunc )


    // --- someWhere in the application code ---
    setDebug( true ); // start the debug
    // ...
    setDebug( false ); // stop the debug

    // --- someWhere else in the application code ---
    startDebugOn("cryptFor"); // --- will start debug like setDebug(true) at the first debug message containing "cryptFor"

be default, without any settings, the debug function do a console.log().

getPublicKeyStoreFromStorage( userIds )

This function is the way to a given keyStore to get anotther public keyStore (for ciphering) by example.

its scope is local to each keyStore and is given at the constructor

parameter type Description
 userIds Array of strings The list of wanted keyStores

This function must return a promise, which return a array of object ( keyStore public content ).

    import { KeyStore } from '@lybero/lybcrypt';


    let getPublicKeyStoresFromServer = (userIds) => {
       return httpGet("api/v1/publickeystores?id=" + userIds.join(','));
    }

    var aliceKeyStore = new KeyStore(aliceId, getPublicKeyStoresFromServer );

In this example, this is the minimal REST api to get keyStores from the database. Note : The server side is not describe in this example. Note : This function must probably do some tests and verifications.

The keyStore do some caching to not ask a lot of time the same keyStore.

This function is called when :

  • ciphering for someone (the recipient keyStore is needed). See cryptFor.
  • signing someone key. See signKey.
  • In quorum initialisation. See initAndShare.

ASK.CRYPTKEYCHOICE

This is the choice for decyphering. In some case, data are encrypted with more than one key and ther is some choice. The user can chose : * A list of key (keyId) he want to use for deciphering (at least one) * Giving a passphrase for each of them (it can have different passphrase for each keys) * Giving a uniq passhrase to use with all selected keys.

If you want, you can avoid to ask passphrase per keys

Example in a console.log style, but you can do some angular, react, jquery or whatever you want !

    // ...
        case ASK.CRYPTKEYCHOICE:
            console.log("I need you for key choice for decyphering ( and private key ) :");
            console.log("This is the available keys :");
            let i = 1;
            for (let keyId in interact.keys) {
                console.log(" "+i+"- "+ interact.keys[keyId].name+" ("+keyId+") type "+interact.keys[keyId].type);
                i++;
            }

            // --- the user select and enter passphrase(s) ----
            answer = {
                    "selectedKeyIds": keyIds,
                    "globalPassphrase": passphrase,
                    "keyPassphrase": {},
                };

            resolve( answer )
    // ...

ASK.SIGNKEYCHOICE

This is the choice when signing keys. If the user has more than one key, he must choose one or more, ang give passhrase(s). The user can chose : * A list of key (keyId) he want to use for sign (at least one) * Giving a passphrase for each of them (it can have different passphrase for each keys) * Giving a uniq passhrase to use with all selected keys.

If you want, you can avoid to ask passphrase per keys

Example in a console.log style, but you can do some angular, react, jquery or whatever you want !

    // ...
        case ASK.SIGNKEYCHOICE:
            console.log("I need you for key choice for signing ( and private key ) :");
            console.log("Available keys :");
            let i = 1;
            for (let keyId in interact.keys) {
                console.log(" "+i+"- "+ interact.keys[keyId].name+" ("+keyId+") type "+interact.keys[keyId].type);
                i++;
            }

            // --- the user select and enter passphrase(s) ----
            answer = {
                    "selectedKeyIds": keyIds,
                    "globalPassphrase": passphrase,
                    "keyPassphrase": {},
                };

            resolve( answer )
    // ...

ASK.ACCEPTUNTRUST

If you want to cryptFor a untrusted user (ie a user with public keys not signed by you or by someone you trust), when the trustLevel of the application id TRUSTLEVEL.medium, the keyStore will ask confirmation for ciphering and ask if you want to sign this user public Key.

            case ASK.ACCEPTUNTRUST:
            console.log("+ Crypt for User "+interact.userName+" keys :");
                for (let keyId in interact.keys) {
                    console.log("  -" + keyId + " | " + interact.keys[keyId].name);
                }
                console.log("Do you want to sign "+interact.userName+"',s pubkeys :");

                for (let keyId in interact.pubKeys) {
                    console.log("  -" + keyId + " | " + interact.pubKeys[keyId].name);
                }
                // --- ask the user here ---

                answer = {
                    "selectedKeyIds": Object.keys(interact.keys),
                    "signKeyIds": Object.keys(interact.pubKeys),
                };
            })
    // ...

Crypto API Reference

lybcrypt is a cryptographic library for symmetric, asymmetric and asymmetric with quorum encryption/decryption. It contains:

  • Symmetric cryptography
    • AES-128-CBC
    • AES-256-CBC

It can use both internal cryptographic routines (nodejs) and browser routines if available (windows.crypto). For asymmetric cryptography, windows.crypto is 6-7 times faster than nodesjs. But it becomes visible only when processing data over 10 kilobytes.

  • Hash
    • SHA-256
    • SHA-512
  • Password encryption
    • pbkdf2
  • Asymmetric signature
    • ECDSA signature P256 curve with SHA-256
    • ECDSA signature P256 curve with SHA-512
  • Asymmetric encryption
    • ElGamal encryption (modp5, modp14, modp15, modp16, modp17, modp18, modp22, modp23, modp24)
    • RSA_OAEP

Hash

Hash produces a hashed string from a text, and allows to verify the hash value.

hash (string)

To hash a String :

    import { Hash } from '@lybero/lybcrypt';

// use Hash
    var cc = new Hash('sha512');
    cc.hash(text)
    .then( (h) => {
        // Do what you want with h (string)
    }

verify (src, hashedString)

To verify a hash :

    cc.verify( src, h)
    .then ( (res) => {
        // true if ok
    })

Password

Password is used for authentication.

pkpbf2 (string)

To register (change) a password for a user :

    import { Password } from '@lybero/lybcrypt';
    // use Password
    var hh = new Password();
    // generate a struct for password "mypassword"
    hh.pkpbf2( "mypassword" )
    .then ( () => {
        return hh.extract();
    })
    .then ((pasStruct) => {
        // ---- Save the passStruct in your dataBase
    });

pkpbf2Verify (string, object)

To register (change) a password for a user :

    import { Password } from '@lybero/lybcrypt';
    // use Password
    var hh = new Password();
    // passStruct is the struct generate by [pkpbf2](#API_pkpbf2)

    hh.pkpbf2Verify( "mypassword" , pasStruct )
    .then ( (resp) => {
        // true if ok
    });

SymCrypt

input / output formats

Output format to cipher - input format to decipher

  • raw : The data is an object like

        { 
            "rawData" : encrypted data (Uint8Array),
            "iv" : initialisation vector (Uint8Array),
            "algo" : algorithm (string)
        }
  • hex : The data is a string encoded as {algorithm}{initialisation vector (base64 string)}data encoded in hexadecimal
  • base64 : The data is a string encoded as {algorithm}{initialisation vector (base64 string)}data encoded in base64
  • struct : The data is an object like

        {
            "rawData" : encrypted data (hexadecimal string),
            "iv" : initialisation vector (base64 string),
            "algo" : algorithm (string) 
        }

Output format to decipher

  • raw : The data is Uint8Array
  • string : The data is a UTF8 string

generate()

To generate internally a symmetric key.

    import { SymCrypt } from '@lybero/lybcrypt';
    // use SymCrypt
    var sym = new SymCrypt();
    sym.key.generate()
    .then ( ( ) => {
        // do what you want
    })

extract()

To extract a key :

    import { SymCrypt } from '@lybero/lybcrypt';
    // use SymCrypt
    var sym = new SymCrypt();
    sym.key.generate()
    .then ( () => {
        return key.extract()
    })
    .then ( (k) => {
        // do what you want with k
    })

wrapKeyFromPassphrase(string)

To derivate a AES key from a string ( typically a pass-phrase)

    import { SymCrypt } from '@lybero/lybcrypt';
    // use SymCrypt
    var sym = new SymCrypt();
    sym.key.wrapKeyFromPassphrase("myPassphrase")
    .then ( () => {
        return key.extract()
    })
    .then ( (k) => {
        // do what you want with k
    })

cipher(clearData, [outputFormat])

  • clearData=string|ArrayBuffer
  • outputFormat = please see Formats "base64"|"hex"|"raw"|"struct" ("base64" by default)

To cypher a buffer (Arraybuffer) or a String

    import { SymCrypt } from '@lybero/lybcrypt';
    // use SymCrypt
    var sym = new SymCrypt();
    sym.key.wrapKeyFromPassphrase("myPassphrase")
    .then ( () => {
        return sym.cipher(secretText, "base64")
    })
    .then ( (crypted) => {
        console.log("crypted in base64", crypted);
        // -> "{AES-256-CBC}{[iv encoded in base64]}{[data encoded in base64]}"
    })
    .then ( () => {
        return sym.cipher(secretText, "struct")
    })
    .then ( (crypted) => {
        console.log("crypted in struct", crypted);
        // -> {
          "data" : [data encoded in hex],
          "iv" : [iv encoded in base64],
          "algo" : "AES-256-CBC"
        }            
    })

decipher( encrypted, inputFormat, ouputFormat, [rawKey])

  • encrypted = encrypted data (string or struct)
  • inputFormat = "raw"|"hex"|"struct"|"base64" (by default) see Formats
  • ouputFormat = "string"|"raw"

To cypher a buffer (Arraybuffer) or a String

    import { SymCrypt } from '@lybero/lybcrypt';
    // use SymCrypt
    var sym = new SymCrypt();
    sym.key.wrapKeyFromPassphrase("myPassphrase")
    .then ( () => {
        return sym.cipher(secretText, "base64")
    })
    .then ( (crypted) => {
      return sym.decipher( crypted, "base64", "string" )
    })
    .then ( (clear) => {
      console.log("clear text = "+clear);
      // clear = secretText
    })

useWindowCrypto

  • By default True

You can avoid using windows.crypto by setting :

    import { SymCrypt } from '@lybero/lybcrypt';
    // use SymCrypt
    var sym = new SymCrypt();
    sym.useWindowCrypto = false;

ElGamal

To use ElGamal :

    import { ElGamal } from '@lybero/lybcrypt';
    var el = ElGamal();

generateKeys()

To generate Keys for a user :

    // Generate keys
    el.generateKeys()

    // protect the privateKey with a passphrase
    .then ( () => {
        return el.cryptPrivateKeyWithPassphrase( passphrase )
    })
    .then ( (encryptedPrivateKeyStruct) => {
        // Save then encrypted private Key Struct where you want
        // and the public Key
        console.log("encrypted key ", encryptedPrivateKeyStruct);
        console.log("public key ", el.publicKeyString);
    })

cryptPrivateKeyWithPassphrase(passphrase)

  • passphrase is a String

To protect the private Key with a passphrase. Return a struct corresponding to a pkpbf2 symetric encryption with AES of the privateKey.

    el.cryptPrivateKeyWithPassphrase( passphrase )
    .then ( (encryptedPrivateKeyStruct) => {
        // Save then encrypted private Key Struct where you want
        // and the public Key
        console.log("encrypted key ", encryptedPrivateKeyStruct);
        console.log("public key ", el.publicKeyString);
    })

importKeys(encryptedPrivateKeyStruct, [publicKeyString] )

  • encryptedPrivateKeyStruct = struct produced by cryptPrivateKeyWithPassphrase
  • publicKeyString = String. The corresponding publicKey

Import keys

    el.importKeys( encryptedPrivateKeyStruct,  publicKeyString );

    // or import only private part
    el.importKeys( encryptedPrivateKeyStruct );

    // or import only public part
    el.publicKeyString = publicKeyString;

publicKeyString = String

Import a public key (but warning, this is not a promise)

    // or import only public part
    el.publicKeyString = publicKeyString;

releasePrivateKey(passphrase)

  • passphrase is a string

To release the privateKey from passphrase

    el.releasePrivateKey( passphrase )
    .then ( () => { })

genSessionKey()

to generate a sessionKey ( a symetric Key) usable to be ciphered in elGamal.

    el.genSessionKey();
    .then ( (sessionKeyElGamal) => {
        // set the key into the symetruc object.
        return sym.key.wrapKeyFromPassphrase(sessionKeyElGamal)
    });

combinedCrypt( string, recipientPublicKey )

To cypher a text (String) for someone:

    // The message to cypher
    var secretText = "this is a secret";

    el.combinedCrypt(secretText, recipientPublicKey )
    .then ( (c) => {
        // c is a structure to send for deciphering
        console.log("combined crypted to send ", c)
    })

combinedDecrypt( dataCrypt, dataKey, keylen, salt, iteration, hash)

To decypher a text : (suppose to have a private Key registered):

    el.combinedDecrypt(c.datasCrypt, c.dataKey, c.keylen, c.salt, c.iteration, c.hash )
    .then ( (clear) => {
      console.log("clear message ", clear)
    })

Example

This is example for ElGamal with symmetric encryption.

Combined cyphering

    let senderEl = new ElGamal();
    let recipientEl = new ElGamal();
    let senderSym = new SymCrypt();
    let AEScrypted = undefined;
    let encryptedSessionKey = undefined;
    let secretText = "IlikeDiscoYeah!"

    recipientEl.generateKeys()
    .then ( () => {
        return senderEl.importKeys(undefined, recipientEl.publicKeyString);
    })
    .then ( (e) => {
        return senderEl.genSessionKey();
    })
    .then ( (sessionKeyElGamal) => {
        return senderSym.key.wrapKeyFromPassphrase(sessionKeyElGamal)
        .then ( () => {
            return senderEl.crypt( sessionKeyElGamal )
        })
        .then ( (c) => {
            encryptedSessionKey = sym.key.params;
            encryptedSessionKey.dataKey = c;
        })
    })
    .then ( () => {
        return senderSym.cipher(secretText, "base64")
    })
    .then ( (k) => {
        AEScrypted = k;      
    })

Combined decyphering

    let senderSym = new SymCrypt();

    recipientEl.decrypt( encryptedSessionKey.dataKey );
    .then ( ( sessionKeyElGamal ) => {
        return senderSym.key.wrapKeyFromPassphrase(sessionKeyElGamal,
            encryptedSessionKey.keylen,
            encryptedSessionKey.salt, encryptedSessionKey.iteration, encryptedSessionKey.hash,
            )
    })
    .then ( () => {
        return senderSym.decipher(AEScrypted, "base64", "string")
    })
    .then ( (clear) => {
        // clear = secretText
    })

Quorum cryptography

This part is dedicated to quorum encryption / decryption. All examples will be done with 3 administrators and a quorum of 2.

Initialisation

A quorum group has members and a public key. An initialisation phase is necessary between the different quorum members. The quorum members must previously have ElGamal public and private keys. A quorum polynomial must be worked out for each member of the quorum.

Once the polynomial is initialised for each quorum group member, a common quorum public key is built by exchanging the public key part of each member of the group to the other members.

The equality of the quorumPublicKey between all the members of the quorum group has then to be tested. Each member of the quorum works out a specific secret shared string for each other member. These shared secret strings must be stored by the different members of the quorum.

Once these operations are done, the quorum group is initialised.

quorumInitPolynome(max, quorum)

  • max is the number of persons in the quorum group.
  • quorum is the number of persons necessary to allow the deciphering of an encrypted data.

quorumAddToPublicKey(aMemberPublicPartString)

  • aMemberPublicPartString is part of the ElGamal data of a user, and was added by the quorumInitPolynome function.

The output of the function is the property publicKeyString.

quorumGetAdminSecretShareString(quorumMemberIndex)

Each quorum member will work out a SecretShareString for each other member of the quorum group in using the index of this other member.

quorumAddToMySecret(secretSharedString)

The SecretShareString that was processed by the other member of a quorum group for the current member must be stored by this member.

Full initialisation sequence

Each administrator must initialise its polynomial and related values. Each administrator must have a different index. Here we initialise the administrator numbered 1 (var me=1). Then, we work out the SecretShareString for each other quorum group member, and we crypt it as well as the publicPart of the public key of the quorum group using the public key of the other quorum group member.

Once these values were created, they must be shared with the corresponding quorum group member:

    var e=new ElGamal();
    var shareForAdmin=[];
    e.quorumInitPolynome(3, 2);

    // set my rank by the way you want ad store where you want
    var me=1; // or 2 or 3

    for ( let x = 1 ; x <= 3 ; x++ ) {
        // ---- Get the public Key from the user rank x by
        // ---- the way you want.
        e.publicKeyString = admins[x].publicKeyString ;
        shareForAdmin[me][x] = {
            "secretShare" : await e.crypt( e.quorumGetAdminSecretShareString(x) ),
            "publicPart" : await e.crypt( e.quorumMyPublicPartString )
        }
        // ---- Send this structure shareForAdmin[me][x] to the user rank x by the admin me.
        // ---- by the way you want
    }

Once this is done, the different quorum group members must create their local version of the quorum public key and verify that they have the same quorum group public key.

This is done in decrypting with their private key the values that were previously provided to them by the other members.

    var e=new ElGamal();
    // set your private Key by the way you want.
    e.privateKey = myprivateKey;

    // get my rank by the way you want
    var me=1; // or 2 or 3

    // ----------- decypher and add shares
    for ( let x = 1 ; x <= 3 ; x++ ) {
        // --- get previous shares from admin x to me
        // --- by the way you want
        let shareFromAdmin = shareForAdmin[x][me];
        let mySecretShare = await e.decrypt( shareFromAdmin.secretShare );
        let myPublicPart = await e.decrypt( shareFromAdmin.publicPart );
        e.quorumAddToMySecret( mySecretShare );
        e.quorumAddToPublicKey( myPublicPart );
    }
    // this is the public Key of the quorum
    let publicKeyString = e.publicKeyString;

    // set your public key by the way you want.
    e.publicKeyString = myPublicKey;
    let quorumKeys = {
        "publicKey" : publicKeyString,
        "privateKey" : await e.crypt ( e.quorumMySecretKeyString )
    };

    // then save the quorum structure (the quorum public
    //  Key and the secret part for administrateur "me"
    //  where you want

Encryption and decryption with the quorum group

As the quorum members share the public key of the quorum group, you just need the public key of any member to do the encryption.

An AES session key has to be generated. This session key is used to crypt the information, and is itself protected by the public key.

genSessionKey()

crypt(string)

To cypher for a quorum, it is similar to a standard combined cyphering activity. The public key (publicKey) generated during the quorum initialisation section.

quorumSurcryptAlpha

Now the quorum group can be used to decrypt the information. At least quorum members of the quorum group must proceed to a surcrypt of the encrypted information.

The different members of the quorum group will proceed to the partial decryption. That’s the aim of the function quorumSurcryptAlpha. They will share these partial decryption in a common structure.

quorumDecrypt

Once quorum surcryptAlpha have been done, it is possible to proceed to the final decryption of the AES key.

In this example, each admin (only 2 of them) does the following sequence (surcrypt the alpha)

    let e = new ElGamal();
    // --- set the privatekey fron where you want
    e.privateKey = myprivateKey;

    // ---- decrypt my private secret for the quorum
    let secret = await e.decrypt(quorumKeys.privateKey);

    // ---------- add the secret to surcrypt the wanted
    e.quorumMySecretKeyString = secret;

    // ---------- surcrypt (wich mean decrypt my part)
    let sur = e.quorumSurcryptAlpha ( cryptedStruct.dataKey );

    // ---------- Save the surcrypt somewhere
    surCrypt[me] = sur;

    // test if the quorum is raised, if yes
    let symSessionKey  =e.quorumDecrypt(cryptedStruct, surCrypt)

Cryptography algorithms and quorums

Creating and encrypting datas

When encrypted data set by a user, the keyStore in the web browser generates an AES256 key randomly m.

The AES256 m key is then encrypted (ElGamal 2048, 4096 or 8192 depending on configuration) with the user's public key creating the file repository and stored on the server. A random salt r is used.

m -> (alpha,beta) = ((g^r mod(p),(Y^r .m)mod(p))

The browser sends to the server the data encoded by the ElGamal algorithm (alpha,beta) . Your application must handle the unencrypted identification metadata (a repository / data set identifier number, including eventually a repository name if necessary; the description, the identifier, the login and the name of each user and quorum groups sharing the repository, the date of creation, the date of last modification and the identifier of the last person who modified the informations).

Decrypting

When a user accesses a data set, (alpha,beta) are retrieved, then the key m is decrypted :

beta / ( (apha ^ S) mod(p) ) -> m

Algorithms associated with quorum groups

Preliminary note

The following calculations are largely inspired by the article: Distributed ElGamal à la Pedersen - Application to Helios par Véronique Cortier (CNRS/LORIA, France), David Galindo (CNRS/LORIA, France), Stéphane Glondu (INRIA Nancy Grand-Est, France) et Malika Izabachène (CNRS/LORIA, France) publié dans WPES 2013 - Proceedings of the 12th ACM workshop on privacy in the electronic society - 2013, Nov 2013, Berlin, Germany. ACM, pp.131-142, 2013.

Initialization of a quorum group

Step 0: creation of the quorum

A right to create a quorum group exists. At least the administrator of the site has this right.

Before using the quorum group, the following configuration parameters must be selected:

  • The name of the quorum group,
  • The list of secret administrators,
  • The quorum associated (the number of Secret Administrators required for recovery with this quorum group).

All these parameters are sent to the server.

Step 1: Generate bi-keys for each secret administrator

If the different administrators do not yet have an account and thus ElGamal bi-keys, they must create an account in order to have an ElGamal key.

Once all secret administrators in the quorum group have a dual key, initialization of the quorum group can continue.

Step 2: Exchange cryptographic parameters

When the secrets administrator n°rank is logging in, he retrieves the base information (identification number, login name, bi-key) and his rank rank in the quorum group.

Each administrator then generates a polynomial of degree quorum decreased by 1 in a discrete integer space.

A part of the public key of the quorum group is calculated by each member of the group. Each secrets administrator rank works out for each other administrator i a specific value using its polynom and the rank of the other administrator . He stores the associated data.

The list of values encrypted with the public key of the corresponding secrets administrator (of rank i) is stored.

Step 3: Generation of the public key of the quorum group

Once all values and are received by the server, the values are exchanged by the different secrets administrators. They work out the public key of the quorum group in decrypting with their private key the different and combining them.

The server provides this quorum group public key as soon as requested. When encryption is required for the quorum group, this public key is used as described above.

Recovery of a secret

Recovering a secret is done through a quorum of secret administrators operating independently of each other, but in the same configurable time window (for example 1 hour).

Step 0: Partial decryption of a secret administrator

The web browser of the secrets administrator of rank rank retrieves all information regarding the quorum group which are all public keys of the secrets administrators and all .

Thanks to its private key, it can decipher and get all provided by all administrators i. He can then compute the secret for the current secret administrator numbered rank.

Step 1: retrieval of the encrypted information

The secret administrator recovers all the information of the secret currently decrypted. This includes α.

Step 2: Over-Encryption

The rank rank administrator's browser works out Crank as a polynom . It sends Crank to the server, encrypted for each administrator using the rank administrator's public key. The server stores all enci(Crank). Each member of quorum waits for all these values. We note the set of administrators participating in the quorum τ and the set of indices corresponding to: τ =[i1, ..., iquorum] .

Once the quorum reached, the server returns to each quorum secrets administrator i who took part to the quorum, the values enci(Crank) (with τ ), and β the data encrypted with the session key m.

Step 3: Local decryption

The rank j administrator's browser works out the session key m:

m is the session key needed to decrypt the encrypted data. Once decrypted the data can be made available to the secret administrator.

In the previous description, we considered that the person requesting a recovery is one of the secrets administrators. However, it can be a third party. In this case, β is encrypted with the public key of the person making the request, and the requester makes the final decryption (step 3 above). The secret administrators can’t decrypt the data.