- Notifications
You must be signed in to change notification settings - Fork74
Secret sharing for javascript
License
grempe/secrets.js
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
- What is it?
- Examples
- Installation and usage
- API
- Share format
- Note on security
- License
- Development and Testing
- Changelog
- Possible future enhancements
secrets.js is an implementation ofShamir's threshold secret sharing scheme in JavaScript, for Node.js and browsers with both Global variable and AMD module loading support.
It can be used to split any "secret" (i.e. a password, text file, Bitcoin private key, anything) inton number of "shares" (each the same size in bits as the original secret), requiring that exactly any numbert ("threshold") of them be present to reconstruct the original secret.
This is a fork of the original excellent code created byamper5and
on Github. Theoriginal secrets.js can be found there.
This code is provided without warranty, as-is and you use it at your own risk.
This library was included in the scope of a security audit performed by Cure53 in July 2019 at the request of theSlant PrivEOS project and no issues were found in this implementation. Our thanks to that team for including this code in its audit scope and for sharing the results with us. A copy of the audit can be foundonline and in theaudit
folder of this repository.
The audit was performed on released code on the master branch no later than commitb6e13cb43f2065a9b622a35002a68222f2bfd437
(Wed Aug 3 21:30:11 2016 -0700
).
Quoting from the Cure53 audit report:
Conclusion...It can be clarified that PrivEOS is based on the cryptographic algorithm called Shamir’sSecret Sharing and has been implemented with an EOS smart contract....Next up, the aforementioned implementation of Shamir's Secret Sharing was auditedand proven to correctly adhere to its specification. Similarly good verdict was reachedabout distribution of Shamir’s Secret Sharing.
Divide a 512-bit key, expressed in hexadecimal form, into 10 shares, requiring that any 5 of them are necessary to reconstruct the original key:
// generate a 512-bit keyvarkey=secrets.random(512)// => key is a hex string// split into 10 shares with a threshold of 5varshares=secrets.share(key,10,5)// => shares = ['801xxx...xxx','802xxx...xxx','803xxx...xxx','804xxx...xxx','805xxx...xxx']// combine 4 sharesvarcomb=secrets.combine(shares.slice(0,4))console.log(comb===key)// => false// combine 5 sharescomb=secrets.combine(shares.slice(4,9))console.log(comb===key)// => true// combine ALL sharescomb=secrets.combine(shares)console.log(comb===key)// => true// create another share with id 8varnewShare=secrets.newShare(8,shares)// => newShare = '808xxx...xxx'// reconstruct using 4 original shares and the new share:comb=secrets.combine(shares.slice(1,5).concat(newShare))console.log(comb===key)// => true
Divide a password containing a mix of numbers, letters, and other characters, requiring that any 3 shares must be present to reconstruct the original password:
varpw="<<PassWord123>>"// convert the text into a hex stringvarpwHex=secrets.str2hex(pw)// => hex string// split into 5 shares, with a threshold of 3varshares=secrets.share(pwHex,5,3)// combine 2 shares:varcomb=secrets.combine(shares.slice(1,3))//convert back to UTF string:comb=secrets.hex2str(comb)console.log(comb===pw)// => false// combine 3 shares:comb=secrets.combine([shares[1],shares[3],shares[4]])//convert back to UTF string:comb=secrets.hex2str(comb)console.log(comb===pw)// => true
There are some additional examples of simple usage in the browser, Node.js, and AMD loading (require.js) in theexamples
folder.
This fork of secrets.js is available fromwww.npmjs.com. Install using
npm install secrets.js-grempe
The source code for this package is available onGithub.
To use it in a Node.js application (Requires OpenSSL support compiled into Node):
varsecrets=require("secrets.js")
To use it in the browser with the global 'secrets' defined, includesecrets.js orsecrets.min.js in your HTML.
<scriptsrc="secrets.min.js"></script>
You can also use it in the browser with an AMD module loading tool likerequire.js. See the AMD loading example in theexamples
dir.
- secrets.share()
- secrets.combine()
- secrets.newShare()
- secrets.init()
- secrets.getConfig()
- secrets.extractShareComponents()
- secrets.setRNG()
- secrets.random()
- secrets.str2hex()
- secrets.hex2str()
Divide asecret
expressed in hexadecimal form intonumShares
number of shares, requiring thatthreshold
number of shares be present for reconstructing thesecret
;
secret
: String, required: A hexadecimal string.numShares
: Number, required: The number of shares to compute. This must be an integer between 2 and 2^bits-1 (seesecrets.init()
below for explanation ofbits
).threshold
: Number, required: The number of shares required to reconstruct the secret. This must be an integer between 2 and 2^bits-1 (seesecrets.init()
below for explanation ofbits
).padLength
: Number, optional, default128
: How much to zero-pad the binary representation ofsecret
. This ensures a minimum length for each share. See "Note on security" below.
The output ofsecrets.share()
is an Array of lengthnumShares
. Each item in the array is a String. SeeShare format
below for information on the format.
Reconstructs a secret fromshares
.
shares
: Array, required: An Array of shares. The form is equivalent to the output fromsecrets.share()
.
The output ofsecrets.combine()
is a String representing the reconstructed secret. Note that this function will ALWAYS produce an output String. However, if the number ofshares
that are provided is not thethreshold
number of shares, the outputwill not be the originalsecret
. In order to guarantee that the original secret is reconstructed, the correctthreshold
number of shares must be provided.
Note that usingmore than thethreshold
number of shares will also result in an accurate reconstruction of the secret. However, using more shares adds to computation time.
Create a new share from the input shares.
id
: Number or String, required: A Number representing the share id. The id is an integer between 1 and 2^bits-1. It can be entered as a Number or a number String expressed in hexadecimal form.shares
: Array, required: The array of shares (in the same format as outputted fromsecrets.share()
) that can be used to reconstruct the originalsecret
.
The output ofsecrets.newShare()
is a String. This is the same format for the share thatsecrets.share()
outputs. Note that this function ALWAYS produces an output String. However, as forsecrets.combine()
, if the number ofshares
that are entered is not thethreshold
number of shares, the output sharewill not be a valid share (i.e.will not be useful in reconstructing the original secret). In order to guarantee that the share is valid, the correctthreshold
number of shares must be provided.
Set the number of bits to use for finite field arithmetic.
bits
: Number, optional, default8
: An integer between 3 and 20. The number of bits to use for the Galois field.rngType
: String, optional: A string that has one of the values["nodeCryptoRandomBytes", "browserCryptoGetRandomValues"]
. Setting this will try to override the RNG that would be selected normally based on feature detection. Warning: You can specify a RNG that won't actuallywork in your environment.
Internally, secrets.js uses finite field arithmetic in binary Galois Fields of size 2^bits. Multiplication is implemented by the means of log and exponential tables. Before any arithmetic is performed, the log and exp tables are pre-computed. Each table contains 2^bits entries.
bits
is the limiting factor onnumShares
andthreshold
. The maximum number of shares possible for a particularbits
is (2^bits)-1 (the zeroth share cannot be used as it is thesecret
by definition.). By default, secrets.js uses 8 bits, for a total 2^8-1 = 255 possible number of shares. To compute more shares, a larger field must be used. To compute the number of bits you will need for yournumShares
orthreshold
, compute the log-base2 of (numShares
+1) and round up, i.e. in #"auto">Note:
- You can call
secrets.init()
anytime to resetall internal state and re-initialize. secrets.init()
does NOT need to be called if you plan on using the default of 8 bits. It is automatically called on loading the library.- The size of the exp and log tables depends on
bits
(each has 2^bits entries). Therefore, using a large number of bits will cause a slightly longer delay to compute the tables. - Thetheoretical maximum number of bits is 31, as JavaScript performs bitwise operations on 31-bit numbers. A limit of 20 bits has been hard-coded into secrets.js, which can produce 1,048,575 shares. secrets.js has not been tested with this many shares, and it is not advisable to go this high, as it may be too slow to be of any practical use.
- The Galois Field may be re-initialized to a new setting when
secrets.newShare()
orsecrets.combine()
are called with shares that are from a different Galois Field than the currently initialized one. For this reason, usesecrets.getConfig()
to check what the currentbits
setting is.
Returns an Object with the current configuration. Has the following properties:
bits
: [Number] The number of bits used for the current initialized finite fieldradix
: [Number] The current radix (Default: 16)maxShares
: [Number] The max shares that can be created with the currentbits
. Computed asMath.pow(2, config.bits) - 1
hasCSPRNG
: [Boolean] Indicates whether or not a Cryptographically Secure Pseudo Random Number Generator has been found and initialized.typeCSPRNG
: [String] Indicates which random number generator function has been selected based on either environment feature detection (the default) or by manually specifying the RNG type usingsecrets.init()
orsecrets.setRNG()
. The current possible types that can be displayed here are ["nodeCryptoRandomBytes", "browserCryptoGetRandomValues"].
Returns an Object with the extracted parts of a public share string passed as an argument. Has the following properties:
bits
: [Number] The number of bits configured when the share was created.id
: [Number] The ID number associated with the share when created.data
: [String] A hex string of the actual share data.
Set the pseudo-random number generator used to compute shares.
secrets.js uses a PRNG in thesecrets.share()
andsecrets.random()
functions. By default, it tries to use a cryptographically strong PRNG. In Node.js this iscrypto.randomBytes()
. In browsers that support it, it iscrypto.getRandomValues()
(using typed arrays, which must be supported too).
To supply your own PRNG, usesecrets.setRNG()
. It expects a Function of the formfunction(bits){}
. It should compute a random integer between 1 and 2^bits-1. The output must be a String of lengthbits
containing random 1's and 0's (cannot be ALL 0's). Whensecrets.setRNG()
is called, it tries to check the PRNG to make sure it complies with some of these demands, but obviously it's not possible to run through all possible outputs. So make sure that it works correctly.
rngType
: String, optional: A string that has one of the values["nodeCryptoRandomBytes", "browserCryptoGetRandomValues"]
. Setting this will try to override the RNG that would be selected normally based on feature detection. Warning: You can specify a RNG that won't actuallywork in your environment.
Generate a randombits
length string, and output it in hexadecimal format.bits
must be an integer greater than 1.
Convert a UTF stringstr
into a hexadecimal string, usingbytesPerChar
bytes (octets) for each character.
str
: String, required: A UTF string.bytesPerChar
: Number, optional, default2
. The maximumbytesPerChar
is 6 to ensure that each character is represented by a number that is below JavaScript's 2^53 maximum for integers.
Convert a hexadecimal string into a UTF string. Each character of the output string is represented bybytesPerChar
bytes in the Stringstr
. See note onbytesPerChar
undersecrets.str2hex()
above.
Each share is a string in the format<bits><id><value>
. Each part of the string is described below:
bits
: The first character, expressed in Base36 format, is the number of bits used for the Galois Field. This number must be between 3 and 20, expressed by the characters [3-9, a-k] in Base36.id
: The id of the share. This is a number between 1 and 2^bits-1, expressed in hexadecimal form. The number of characters used to represent the id is the character-length of the representation of the maximum id (2^bits-1) in hexadecimal:(Math.pow(2,bits)-1).toString(16).length
.data
: The value of the share, expressed in hexadecimal form. The length of this string depends on the length of the secret.
You can extract these attributes from a share in your possession with thesecrets.extractShareComponents(share)
function which will return an Object with these attributes. You may use these values, for example, to callsecrets.init()
with the proper bits setting for shares you want to combine.
Shamir's secret sharing scheme is "information-theoretically secure" and "perfectly secure" in that less than the requisite number of shares provide no information about the secret (i.e. knowing less than the requisite number of shares is the same as knowing none of the shares). However, because the size of each share is the same as the size of the secret (when using binary Galois fields, as secrets.js does), in practice it does leaksome information, namely thesize of the secret. Therefore, if you will be using secrets.js to shareshort password strings (which can be brute-forced much more easily than longer ones), it would be wise to zero-pad them so that the shares do not leak information about the size of the secret. With this in mind, secrets.js will zero-pad in multiples of 128 bits by default which slightly increases the share size for small secrets in the name of added security. You can increase or decrease this padding manually by passing thepadLength
argument tosecrets.share()
.
Whensecrets.share()
is called with apadLength
, thesecret
is zero-padded so that it's length is a multiple of the padLength. The second example above can be modified to use 1024-bit zero-padding, producing longer shares:
varpw="<<PassWord123>>"// convert the text into a hex stringvarpwHex=secrets.str2hex(pw)// => 240-bit password// split into 5 shares, with a threshold of 3, WITH zero-paddingvarshares=secrets.share(pwHex,5,3,1024)// => 1024-bit padded shares// combine 3 sharesvarcomb=secrets.combine([shares[1],shares[3],shares[4]])// convert back to UTF stringcomb=secrets.hex2str(comb)console.log(comb===pw)// => true
There is aworkbook containing some additional padding examples.
secrets.js is released under the MIT License. See theLICENSE
file.
cd secrets.js/npm install
Watch all JavaScript files and run the testing and minification tasks on every save to a file.
npm run watch
The minified version of thesecrets.js
can be found insecrets.min.js
. This file was generated using theUglifyJS2 tool.
npm run build
You can also run the Jasmine test suite within a Node.js instance.
npm runtest
There is aJasmine test suite that exercises the entiresecrets
module that can be run against the source code or the minified version.
npm run test-browsernpm run test-browser-min
2.0.0
- [BREAKING] Removed SJCL random number generator. Modern browsers have all the support they need for
crypto.getRandomValues()
. - Modernize build and test process
- Lint and Prettier JS and Markdown files
- Implement
npm run ...
scripts for common tests and build tasks - Remove global dev dependencies
- Remove references to, and use of, Bower installation
- [BREAKING] Removed SJCL random number generator. Modern browsers have all the support they need for
1.2.0
- Added
secrets.seedRNG()
function to allow seeding the SJCL RNG instantly via Browser or Node.js RNG's or with entropy from an external server.
- Added
1.1.0
- Added
grunt watch
task to auto-run tests and minification on every JavaScript file save. - Minified file now contains name, version and author comments automatically.
- Configured basic
grunt
tasks for minification, Node.js testing with Jasmine, jshint, eslint. Removed Karma test runner and manual minification and testing steps. Just rungrunt
. - [Bugfix] calling
secrets.init()
now actually resetsall internal state back to the default settings. Previouslyinit()
only reset some internal values.init()
now calls a new private functionreset()
to accomplish this. - [Enhancement] If theStanford Javascript Crypto Libarary (SJCL) is loaded in the browser it can be used as a fallback, or explicitly selected, CSPRNG for those browsers that don't support
crypto.getRandomValues()
. It uses the Fortuna RNG and collects additional entropy from mouse movements continually. The downside is that it requires mouse movements initially beforesecrets.random()
can be called.secrets.random()
will throw an Error if called when SJCL is not fully seeded. Currently set to use the maximum SJCL 'paranoia' level of 10. An enhancement to this might be to call out to retrieve one or more external sources of entropy (and mixing them together) to pre-seed the RNG when the library is loaded. - [Enhancement] You can now pass a string to
init()
orsetRNG()
which forces loading of a specific RNG (whether it will work or not in your current env!) * Re-factored howgetRNG()
works internally. Now it returns small focused functions, not a giant function with detection conditionals. If SJCL is loaded the RNG tests are skipped since they would always initially fail due to the entropy pool being initally empty. This should be OK for this 'trusted' RNG.
- Added
1.0.0
- Packaging cleanup and ready for 1.0.0 release on Bower and NPM.
- [Enhancement] Now supports the Javascript Universal Module DefinitionUMDJS for loading this module in the Browser with a
secrets
global, using an AMD Module loader like require.js, or in Node.js apps. - Refactor getRNG() to no longer have embedded
require
now that crypto is included on module load with the UMDJS change. - Updated README.md with info about this fork of secrets.js. * Added some simple examples of usage to the examples folder.
0.2.0
- [Enhancement] Extend the output of getConfig() to include the
radix
andmaxShares
properties. - [Security] Zero-pad all secrets in multiples of 128 bits (instead of 0) by default.
- [Performance] Massive (100x) speed optimization to padLeft() private function (the second most frequently called block of code internally).
- [Testing] Added a full jasmine test suite and Karma test runner. Karma runs will also generate code coverage HTML reports. Code coverage is currently >90%.
- [Testing] Expose all private functions as Underscore (_) prefixed functions to allow direct unit testing.
- [Security] Removed Math.random fallback random number generator. Should always fail safe, even if it means not working.
secrets.getConfig().unsafePRNG
will always result in undefined now as it is no longer ever set. - Refactored away need to know anything about
global
var. - [Testing] jslint.com, jshint.com, and eslint CLI warnings for code and style now clean. - Beautify code.
- [Enhancement] Extend the output of getConfig() to include the
0.1.8: bugfix release
0.1.7: added config.unsafePRNG reset when supplying a new PRNG
0.1.6:
- Removed JSBN dependency, support for arbitrary radices, and the
convertBase()
function, with attendant 50% file size reduction. - Fixed bug where leading zeros were dropped. * Renamed string conversion functions.
- Removed JSBN dependency, support for arbitrary radices, and the
0.1.5: getConfig() returns information about PRNG
0.1.4: new share format
About
Secret sharing for javascript
Resources
License
Stars
Watchers
Forks
Packages0
Languages
- JavaScript96.2%
- HTML3.8%