Comparing version 1.7.4 to 2.0.0
117
index.js
@@ -1,1 +0,116 @@ | ||
module.exports = require('./build/Release/scrypt'); | ||
var scrypt = require('./build/Release/scrypt'); | ||
// | ||
//Create function instances | ||
// | ||
scrypt.passwordHash = scrypt.Hash(); | ||
scrypt.verifyHash = scrypt.Verify(); | ||
scrypt.hash = scrypt.Hash(); | ||
scrypt.verify = scrypt.Verify(); | ||
scrypt.params = scrypt.Params(); | ||
scrypt.kdf = scrypt.KDF(); | ||
// | ||
//Parses input arguments for scrypt parameter object or translation function inputs | ||
// | ||
function parseScryptParameters(args, startIndex) { | ||
var i = 0, | ||
paramsObject = {}; | ||
for (i=startIndex; i < startIndex+3 && typeof args[i] != "undefined"; i++) { | ||
if (i - startIndex > 0 && (typeof args[i] === "function" || typeof args[i] === "boolean")) { | ||
break; | ||
} | ||
switch(typeof args[i]) { | ||
case "number": | ||
if (i - startIndex == 0) { | ||
paramsObject.maxtime = args[i]; | ||
} | ||
if (i - startIndex == 1) { | ||
paramsObject.maxmem = args[i]; | ||
} | ||
if (i - startIndex == 2) { | ||
paramsObject.maxmemfrac = args[i]; | ||
} | ||
break; | ||
default: | ||
if (i-startIndex == 0) { | ||
throw scrypt.errorObject(1, "expecting maxtime as a number"); | ||
} | ||
if (i-startIndex == 1) { | ||
throw scrypt.errorObject(1, "expecting maxmem as a number"); | ||
} | ||
if (i-startIndex == 2) { | ||
throw scrypt.errorObject(1, "expecting maxmemfrac as a number"); | ||
} | ||
break; | ||
} | ||
} | ||
return paramsObject; | ||
} | ||
// | ||
// Scrypt Password Hash | ||
// | ||
scrypt.passwordHash = function(passwordHash, params) { | ||
var retFunction = function() { | ||
var args = Array.prototype.slice.apply(arguments), | ||
paramsObject; | ||
//Determine if there are too little arguments | ||
if (args.length < 2) { | ||
throw scrypt.errorObject(1, "wrong number of arguments - at least two arguments are needed - key and scrypt parameters JSON object"); | ||
} | ||
//Determine if translation function is needed | ||
if (args.length > 1 && typeof args[1] !== "object" && typeof args[1] !== "function") { | ||
paramsObject = parseScryptParameters(arguments, 1); | ||
} | ||
//Asyc | ||
if (typeof args[args.length-1] === "function") { | ||
if (typeof paramsObject !== "undefined") { | ||
params(paramsObject.maxtime, paramsObject.maxmem, paramsObject.maxmemfrac, function(err, scryptParams) { | ||
args.splice(1,Object.keys(paramsObject).length,scryptParams); | ||
passwordHash(args[0], args[1], args[2]); | ||
}); | ||
} else { | ||
passwordHash(args[0], args[1], args[2]); | ||
} | ||
//Sync | ||
} else { | ||
if (typeof paramsObject !== "undefined") { | ||
var scryptParams = params(paramsObject.maxtime, paramsObject.maxmem, paramsObject.maxmemfrac); | ||
args.splice(1, Object.keys(paramsObject).length, scryptParams); | ||
} | ||
return passwordHash(args[0], args[1]); | ||
} | ||
} | ||
retFunction.config = passwordHash.config; | ||
return retFunction; | ||
}(scrypt.passwordHash, scrypt.params); | ||
// | ||
// Backward Compatbility | ||
// | ||
scrypt.passwordHash.config.keyEncoding = "ascii"; | ||
scrypt.passwordHash.config.outputEncoding = "base64"; | ||
scrypt.verifyHash.config.hashEncoding = "base64"; | ||
scrypt.verifyHash.config.keyEncoding = "ascii"; | ||
scrypt.passwordHashSync = scrypt.passwordHash; | ||
scrypt.verifyHashSync = scrypt.verifyHash; | ||
module.exports = scrypt; |
{ | ||
"name": "scrypt", | ||
"description": "The scrypt crypto library for NodeJS", | ||
"version": "1.7.4", | ||
"keywords": [ | ||
"scrypt", | ||
"password", | ||
"auth", | ||
"authentication", | ||
"encryption", | ||
"crypto" | ||
], | ||
"author": { | ||
"name": "Barry Steyn", | ||
"email": "barry.steyn@gmail.com", | ||
"url": "http://doctrina.org" | ||
}, | ||
"engines" : { | ||
"node": ">= 0.8.0" | ||
}, | ||
"repository" : { | ||
"type": "git", | ||
"url": "https://github.com/barrysteyn/node-scrypt.git" | ||
}, | ||
"devDependencies": { | ||
"tap": "*" | ||
}, | ||
"licenses": [ | ||
{ | ||
"type": "MIT" | ||
} | ||
], | ||
"bugs": { | ||
"url": "https://github.com/barrysteyn/node-scrypt/issues" | ||
}, | ||
"scripts" : { | ||
"test": "npm install && node tests/scrypt-tests.js" | ||
} | ||
"name": "scrypt", | ||
"description": "The scrypt crypto library for NodeJS", | ||
"version": "2.0.0", | ||
"keywords": [ | ||
"scrypt", | ||
"password", | ||
"auth", | ||
"authentication", | ||
"encryption", | ||
"crypto", | ||
"secret", | ||
"key", | ||
"secret key", | ||
"hash", | ||
"verify" | ||
], | ||
"author": { | ||
"name": "Barry Steyn", | ||
"email": "barry.steyn@gmail.com", | ||
"url": "http://doctrina.org" | ||
}, | ||
"engines" : { | ||
"node": ">= 0.10.0" | ||
}, | ||
"repository" : { | ||
"type": "git", | ||
"url": "https://github.com/barrysteyn/node-scrypt.git" | ||
}, | ||
"devDependencies": { | ||
"tap": "*" | ||
}, | ||
"licenses": [ | ||
{ | ||
"type": "MIT" | ||
} | ||
], | ||
"bugs": { | ||
"url": "https://github.com/barrysteyn/node-scrypt/issues" | ||
}, | ||
"scripts" : { | ||
"test": "npm install && node tests/scrypt-tests.js" | ||
} | ||
} |
609
Readme.md
@@ -5,17 +5,19 @@ #Scrypt For NodeJS | ||
node-scrypt is a native node C++ wrapper for Colin Percival's Scrypt [key derivation](http://en.wikipedia.org/wiki/Key_derivation_function) utility. In short, it is a NodeJS module for what is arguably the most advanced password hash in existence. | ||
node-scrypt is a native node C++ wrapper for Colin Percival's Scrypt utility. | ||
##Platforms Supported | ||
For Scrypt to work at its best, it needs to have its configuration file custom built for each platform it is installed on. Scrypt's author made Scrypt as a C program. The user of this C program is expected to run a specialised configuration script which will automatically determines the best and most secure way Scrypt can be compiled on that platform. In the past, the output of this configuration script run on a Linux box was used with this module, with other platforms (notably Mac OS) being specially customised. | ||
As should be the case with any security tool, this library should be scrutinized by anyone using it. If you find or suspect an issue with the code- please bring it to my attention and I'll spend some time trying to make sure that this tool is as secure as possible. | ||
As of version 1.6.0, this configuration is run automatically before each compile, meaning that this NodeJS Scrypt module will be perfectly tuned to the target operating system. But this is only available for Unix like platforms (Windows support coming soon). It has been tested on **Linux**, **MAC OS** and **SmartOS** (so its ready for Joyent Cloud). This includes FreeBSD, OpenBSD, SunOS etc. | ||
##Table Of Contents | ||
**New In Version 1.7.0**: Version 1.7.0 now automatically determines what libraries (besides openssl) to link to. Therefore I expect this module to work on any *unix-like* platform. | ||
* [Scrypt](#scrypt) | ||
* [Installation Instructions](#installation-instructions) | ||
* [Introducing Node-scrypt version 2.X](#introducing-node-scrypt-version-2) | ||
* [API](#api) | ||
* [Example Usage](#example-usage) | ||
* [FAQ](#faq) | ||
* [Credits](#credits) | ||
##Node Version Compatibilty | ||
This module supports Node version 0.8x and upwards. Earlier versions of Node do not come bundled with OpenSSL which is required for this module to work. | ||
##Scrypt | ||
Scrypt is an advanced crypto library used mainly for [key derivation](http://en.wikipedia.org/wiki/Key_derivation_function): More information can be found here: | ||
##What Is Scrypt? | ||
Scrypt is an advanced crypto library used mainly for [key derivation](http://en.wikipedia.org/wiki/Key_derivation_function) (i.e. password authenticator). More information can be found here: | ||
* [Tarsnap blurb about Scrypt](http://www.tarsnap.com/scrypt.html) - Colin Percival (the author of Scrypt) explains a bit about it. | ||
@@ -25,69 +27,74 @@ * [Academic paper explaining Scrypt](http://www.tarsnap.com/scrypt/scrypt.pdf). | ||
For additional interest, read the article on Wikipedia about the [key derivation function](http://en.wikipedia.org/wiki/Key_derivation_function). | ||
##Installation Instructions | ||
###Requirements | ||
###The Three Essential Properties Of Password Key Derivation | ||
Password key derivation requires three properties: | ||
* Node version 0.10x and upwards. | ||
* A posix [platform](#what-platforms-are-supported). | ||
* The password must not be stored in plaintext. (Therefore it is hashed). | ||
* The password hash must be salted. (Making a rainbow table attack very difficult to pull off). | ||
* The salted hash function must not be fast. (If someone does get hold of the salted hashes, their only option will be brute force which will be very slow). | ||
###From NPM | ||
This Scrypt library automatically handles the above properties. The last item seems strange: Computer scientists are normally pre-occupied with making things fast. Yet it is this property that sets Scrypt apart from the competition. As computers evolve and get more powerful, they are able to attack this property more efficiently. This has become especially apparent with the rise of parallel programming. Scrypt aims to defend against all types of attacks, not matter the attackers power now or in the future. | ||
npm install scrypt | ||
### What This Module Provides | ||
This module implements the following: | ||
###From Source | ||
You will need `node-gyp` to get this to work (install it if you don't have it: `npm install -g node-gyp`): | ||
* **Scrypt password key derivation** | ||
* All three essential properties of password key derivation are implemented (as described above). | ||
* Both *asynchronous* and *synchronous* versions are available. | ||
* **Scrypt encryption** | ||
* Both *asynchronous* and *synchronous* versions are available. | ||
git clone https://github.com/barrysteyn/node-scrypt.git | ||
cd node-scrypt | ||
node-gyp configure build | ||
I suspect Scrypt will be used mainly as a password key derivation function (its author's intended use), but I have also ported the Scrypt encryption and decryption functions as implementations for them were available from the author. Performing Scrypt cryptography is done if you value security over speed. Scrypt is more secure than a vanilla block cipher (e.g. AES) but it is much slower. It is also the basis for the key derivation functions. | ||
###Testing | ||
To test, go to the folder where Scrypt was installed, and type: | ||
### The Scrypt Hash Format | ||
I have included this section because I keep being queried about the randomness of this module. Scrypt (and in general, all key derivation functions) store metadata in the header which cannot be encrypted. For example, the random salt needs to be stored un-encrypted in the header. The header information not being encrypted does not mean that security is weakened. What is essential in terms of security is hash **integrity** (meaning that no part of the hashed output can be changed) and that the original password cannot be determined from the hashed output (this is why you are using Scrypt - because it does this in a good way). Scrypt uses a normal MAC to ensure integrity, but it derives it in a funky way based on its unique properties. | ||
npm test | ||
Every Scrypt header starts with the word *"scrypt"*. The reason for this is that I am following Colin Percival's (Scrypt's author) reference implementation whereby he starts off each hash this way. Next comes information regarding how the hash will be constructed (see the three tweakable inputs below). Users of Scrypt normally do not change this information once it is settled upon (hence this will also look the same for each hash). Once the hash has been produced, the result is base64 encoded to ensure maximum portability. | ||
##Introducing Node-Scrypt Version 2 | ||
This module is a complete rewrite of the previous module. It's main highlights are: | ||
Taking the above paragraph into account, note the following: The base64 encoding for the word *"scrypt"* is *c2NyeXB0*. So at the very least, every hash derived using this module should start with *c2NyeXB0*. Next comes metadata that normally does not change once settled upon (so it should also look the same). Only then does the random salt get added along with the derived hashed password. | ||
To illustrate with an example, I have hashed two password: *password1* and *password2*. Their outputs are as follows: | ||
* Access to the underlying key derivation function | ||
* Extensive use of node's buffers | ||
* Easy configuration | ||
* Removal of scrypt encryption/decryption (this will soon be moved to another module) | ||
password1 | ||
c2NyeXB0AAwAAAAIAAAAAcQ0zwp7QNLklxCn14vB75AYWDIrrT9I/7F9+lVGBfKN/1TH2hs | ||
/HboSy1ptzN0YzHJhC7PZIEPQzf2nuoaqVZg8VkKEJlo8/QaH7qjU2VwB | ||
password2 | ||
c2NyeXB0AAwAAAAIAAAAAZ/+bp8gWcTZgEC7YQZeLLyxFeKRRdDkwbaGeFC0NkdUr/YFAWY | ||
/UwdOH4i/PxW48fXeXBDOTvGWtS3lLUgzNM0PlJbXhMOGd2bke0PvTSnW | ||
The module consists of four functions: | ||
As one can see from the above example, both hashes start off by looking similar (they both start with *c2NyeXB0AAwAAAAIAAAAA* - as explained above), but afterwards, things change very rapidly. In fact, I hashed the password *password1* again: | ||
1. [params](#params) - a translation function that produces scrypt parameters | ||
2. [hash](#hash) - produces a 256 bit hash using scrypt's key derivation function | ||
3. [verify](#verify) - verify's a hash produced by this module | ||
4. [kdf](#key-derivation-function) - scrypt's underlying key dervivation function | ||
password1 | ||
c2NyeXB0AAwAAAAIAAAAATpP+fdQAryDiRmCmcoOrZa2mZ049KdbA/ofTTrATQQ+m | ||
0L/gR811d0WQyip6p2skXVEMz2+8U+xGryFu2p0yzfCxYLUrAaIzaZELkN2M6k0 | ||
It also consists of four extra functions that provide [backward compatbility](#backward-compatibility-for-users-of-version-1x) to the previous version. | ||
Compare this hash to the one above. Even though they start off looking similar, their outputs are vastly different (even though it is the same password being hashed). This is because of the **random** salt that has been added, ensuring that no two hashes will ever be indentical, even if the password that is being hashed is the same. | ||
####Encodings | ||
The following encodings are accepted: | ||
For those that are curious or paranoid, please look at how the hash is both [produced](https://github.com/barrysteyn/node-scrypt/blob/master/src/passwordhash/scrypthash.c#L146-197) and [verified](https://github.com/barrysteyn/node-scrypt/blob/master/src/passwordhash/scrypthash.c#L199-238) (you are going to need some knowledge of the [C language](http://c.learncodethehardway.org/book/) for this). | ||
1. **ascii** | ||
2. **utf8** | ||
3. **base64** | ||
4. **ucs2** | ||
5. **binary** | ||
6. **hex** | ||
7. **buffer** | ||
##Why Use Scrypt? | ||
It is probably the most advanced key derivation function available. This is is quote taken from a comment in hacker news: | ||
The last encoding is node's [Buffer](http://nodejs.org/api/buffer.html) object. Buffer is useful for representing raw binary data and has the ability to translate into any of the encodings mentioned above. It is for these reasons that encodings default to buffer in this module. | ||
>Passwords hashed with scrypt with sufficiently-high strength values (there are 3 tweakable input numbers) are fundamentally impervious to being cracked. I use the word "fundamental" in the literal sense, here; even if you had the resources of a large country, you would not be able to design any hardware (whether it be GPU hardware, custom-designed hardware, or otherwise) which could crack these hashes. Ever. (For sufficiently-small definitions of "ever". At the very least "within your lifetime"; probably far longer.) | ||
###Params | ||
The [params function](#params-1) translates human understandable parameters to Scrypt's internal parameters. | ||
The *three tweakable* inputs mentioned above are as follows (quoting from Scrypt's author Colin Percival): | ||
The human understandable parameters are as follows: | ||
**maxtime** | ||
>maxtime will instruct scrypt to spend at most maxtime seconds computing the derived encryption key from the password; [If using scrypt] for encryption, this value will determine how secure the encrypted data is, while for decryption this value is used as an upper limit (if scrypt detects that it would take too long to decrypt the data, it will exit with an error message). | ||
1. **maxtime**: the maximum amount of time scrypt will spend when computing the derived key. | ||
2. **maxmemfrac**: the maximum fraction of the available RAM used when computing the derived key. | ||
3. **maxmem**: the maximum number of bytes of RAM used when computing the derived encryption key. | ||
**maxmemfrac** | ||
>maxmemfrac instructs scrypt to use at most the specified fraction of the available RAM for computing the derived encryption key. For encryption, increasing this value might increase the security of the encrypted data, depending on the maxtime value; for decryption, this value is used as an upper limit and may cause scrypt to exit with an error. | ||
Scrypt's internal parameters are as follows: | ||
**maxmem** | ||
>maxmem instructs scrypt to use at most the specified number of bytes of RAM when computing the derived encryption key. | ||
1. **N** - general work factor, iteration count. | ||
2. **r** - blocksize in use for underlying hash; fine-tunes the relative memory-cost. | ||
3. **p** - parallelization factor; fine-tunes the relative cpu-cost. | ||
For info on what the above parameters do, read [section 5 of the scrypt ietf draft](http://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01#section-5). | ||
**A Note On How Memory Is Calculated**: `maxmem` is often defaulted to `0`. This does not mean that `0` RAM is used. Instead, memory used is calculated like so (quote from Colin Percival): | ||
####How Memory Is Calculated | ||
`maxmem` is often defaulted to `0`. This does not mean that `0` RAM is used. Instead, memory used is calculated like so (quote from Colin Percival): | ||
@@ -98,63 +105,310 @@ > the system [will use] the amount of RAM which [is] specified [as the] fraction of the available RAM, but no more than maxmem, and no less than 1MiB | ||
###The Three Tweakable Inputs | ||
<u>**Note**: This is a very important section to understand</u>. The three tweakable inputs mentioned above are actually just *human understandable* inputs into a translation function that produces the inputs required for the internal scrypt cryptographic function. These inputs (as defined in the [scrypt paper](http://www.tarsnap.com/scrypt/scrypt.pdf)) are as follows: | ||
###Hash | ||
The [hash function](#hash-1) does the following: | ||
1. **N** - general work factor, iteration count. | ||
2. **r** - blocksize in use for underlying hash; fine-tunes the relative memory-cost. | ||
3. **p** - parallelization factor; fine-tunes the relative cpu-cost. | ||
* Adds random salt. | ||
* Creates a HMAC to protect against active attack. | ||
* Uses the Scrypt key derivation function to derive a hash for a key. | ||
Values for *maxtime*, *maxmemfrac* and *maxmem* are translated into the above values, which are then fed to the Scrypt function. The translation function also takes into account the CPU and Memory capabilities of a machine. Therefore values of *N*, *r* and *p* may differ for different machines that have different specs. | ||
####Hash Format | ||
All hashes start with the word *"scrypt"*. Next comes the scrypt parameters used in the key derivation function, followed by random salt. Finally, a 256 bit HMAC of previous content is appended, with the key for the HMAC being produced by the scrypt key derivation function. The result is a 768 bit (96 byte) output: | ||
## Pros And Cons | ||
Here are some pros and cons for using it: | ||
1. **bytes 0-5**: The word *"scrypt"* | ||
2. **bytes 6-15**: Scrypt parameters N, r, and p | ||
3. **bytes 16-47**: 32 bits of random salt | ||
4. **bytes 48-63**: A 16 bit checksum | ||
5. **bytes 64-95**: A 32 bit HMAC of bytes 0 to 63 using a key produced by the Scrypt key derivation function. | ||
###Pros | ||
Bytes 0 to 63 are left in plaintext. This is necessary as these bytes contain metadata needed for verifying the hash. This information not being encrypted does not mean that security is weakened. What is essential in terms of security is hash **integrity** (meaning that no part of the hashed output can be changed) and that the original password cannot be determined from the hashed output (this is why you are using Scrypt - because it does this in a good way). Bytes 64 to 95 is where all this happens. | ||
* The Scrypt algorithm has been published by [IETF](http://en.wikipedia.org/wiki/IETF) as an [Internet Draft](http://en.wikipedia.org/wiki/Internet_Draft) and is thus on track to becoming a standard. See [here](https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-00) for the draft. | ||
* It is being actively used in production at [Tarsnap](http://www.tarsnap.com/). | ||
* It is much more secure than bcrypt. | ||
* It is designed to be future proof against attacks with future (and more advanced) hardware. | ||
* It is designed to defend against large scale custom hardware attacks. | ||
* It is production ready. | ||
* There is a Scrypt library for most major scripting languages (Python, Ruby etc). Now this module provides the library for NodeJS :) | ||
###Verify | ||
The [verify function](#verify-1) determines whether a hash can be derived from a given key and returns a boolean result. | ||
I will end this section with a quote from Colin Percival (author of Scrypt): | ||
###Key Derivation Function | ||
The underlying [Scrypt key derivation function](#kdf). This functionality is exposed for users who are quite experienced and need the function for business logic. A good example is [litecoin](https://litecoin.org/) which uses the scrypt key derivation function as a proof of work. The key derivation function in this module is tested against [three of the four test vectors](http://tools.ietf.org/html/draft-josefsson-scrypt-kdf-00#page-11) in the original scrypt paper. The fourth test vector takes too long to computer and is infeasible to use as testing for continuous integration. Nevertheless, it is included in the tests, but commented out - uncomment it and run the tests, but be warned that it is rather taxing on resources. | ||
> We estimate that on modern (2009) hardware, if 5 seconds are spent computing a derived key, the cost of a hardware brute-force attack against scrypt is roughly 4000 times greater than the cost of a similar attack against bcrypt (to find the same password), and 20000 times greater than a similar attack against PBKDF2. | ||
####Use Hash To Store Keys | ||
If your interested in this module is to produce hashes to store passwords, then I strongly encourage you to use the hash function. The key derivation function does not produce any [message authentication code](http://en.wikipedia.org/wiki/Message_authentication_code) to ensure integrity. You will also have to store the scrypt parameters separately. Lastly, there is no native verify function included in this module. | ||
###Cons | ||
There is just one con I can think of: It is a relatively new library (only been around since 2009). Cryptographers don't really like new libraries for production deployment as it has not been *battle tested*. That being said, it is being actively used in [Tarsnap](http://www.tarsnap.com/) (as mentioned above) and the author is very active. | ||
In short: If you are going to use this module to store keys, then use the hash function. It has been customised for general key storage and is both easier to use and provides better protection compared to the key derivation function. | ||
#Security Issues/Concerns | ||
As should be the case with any security tool, this library should be scrutinized by anyone using it. If you find or suspect an issue with the code- please bring it to my attention and I'll spend some time trying to make sure that this tool is as secure as possible. | ||
###Backward Compatibility For User's Of Version 1.x | ||
Four extra functions are provided for means of backward compatibility: | ||
#Installation Instruction | ||
##From NPM | ||
1. [passwordHash](#passwordhash) | ||
2. [passwordHashSync](#passwordhashsync) | ||
3. [verifyHash](#verifyhash) | ||
4. [verifyHashSync](#verifyhashsync) | ||
npm install scrypt | ||
The above functions are defaulted to behave exactly like the previous version. | ||
##From Source | ||
You will need `node-gyp` to get this to work (install it if you don't have it: `npm install -g node-gyp`): | ||
##API | ||
#####A Note On Error Handling | ||
All synchronous functionality should be wrapped in a `try ... catch` as exceptions are thrown in case of error. For asynchronous functionality, error are returned as the first argument to the callback function if such an error exists. An error is an object with both an error code and a message describing the error. | ||
git clone https://github.com/barrysteyn/node-scrypt.git | ||
cd node-scrypt | ||
node-gyp configure build | ||
#####Scrypt Parameter Object | ||
The scrypt parameter object is a JSON object that must have values for properties **N**, **r** and **p**. For example, it could look like this: | ||
#Testing | ||
To test, go to the folder where Scrypt was installed, and type: | ||
{ | ||
N: 1, | ||
r: 1, | ||
p: 1 | ||
} | ||
npm test | ||
###Params | ||
`params(maxtime, maxmem, maxmemfrac, callback_function)` | ||
#Hash Info | ||
All Scrypt output is encoded into Base64 using [René Nyffenegger](http://www.adp-gmbh.ch/) [library](http://www.adp-gmbh.ch/cpp/common/base64.html). The character sets that compromises all output are `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. | ||
* `maxmem` - [OPTIONAL] - an integer, specifying the maximum number of bytes | ||
* `maxmemfrac` - [OPTIONAL] - a double value between 0.0 and 1.0, representing a noramlized percentage value | ||
* `callback_function` - [OPTIONAL] - if present, will make this function asynchronous | ||
#Usage | ||
There are both asynchronous and synchronous functions available. It is highly recommended not to use the synchronous version unless necessary due to the fact that Node's event loop will be blocked for the duration of these purposefully slow functions. | ||
####Params Config Object | ||
The params config object is accessible from `scrypt.params.config`. It has the following default value: | ||
##Asynchronous Authentication | ||
For interactive authentication, set `maxtime` to `0.1` - 100 milliseconds. | ||
###To create a password hash | ||
{ | ||
maxmem: 0, | ||
maxmemfrac: 0.5 | ||
} | ||
* `maxmem` - an integer representing the default value maxmem is set to if not explicitly defined in the function call | ||
* `maxmemfrac` - a double representing the default value maxmemfrac is set to if not explicitly defined in the function call | ||
Read the section on [how memory is calculated](#how-memory-is-calculated) to get a better understanding of these values. | ||
The return value will be a [scrypt parameter object](#scrypt-parameter-object) | ||
###Hash | ||
`hash(key, scrypt_parameters, callback_function)` | ||
* `key` - [REQUIRED] - an [encoded string or buffer](#encodings) representing the key to be hashed | ||
* `scrypt_parameters` - [REQUIRED] - a JSON object representing the [scrypt's internal parameters](#params) | ||
* `callback_function` - [OPTIONAL] - if present, will make this function asynchronous | ||
####Hash Config Object | ||
The hash config object is accessible from `scrypt.hash.config`. It has the following default value: | ||
{ | ||
keyEncoding: 'buffer', | ||
outputEncoding: 'buffer' | ||
} | ||
* `keyEncoding` - a string representing the [encoding](#encodings) of the input key | ||
* `outputEncoding` - a string representing the [encoding](#encodings) of the output returned to the user | ||
The return value will be an [encoded string or buffer](#encodings) of the [hash format](#hash-format). | ||
###Verify | ||
`verify(hash, key, callback_function)` | ||
* `hash` - [REQUIRED] - an [encoded string or buffer](#encodings) of the output of the hash function | ||
* `key` - [REQUIRED] - an [encoded string or buffer](#encodings) representing the key to be hashed | ||
* `callback_function` - [OPTIONAL] - if present, will make this function asynchronous | ||
####Verify Config Object | ||
The verify config object is accessible from `scrypt.verify.config`. It has the following default value: | ||
{ | ||
hashEncoding: 'buffer', | ||
keyEncoding: 'buffer' | ||
} | ||
* `hashEncoding` - a string representing the [encoding](#encodings) of the input hash | ||
* `keyEncoding` - a string representing the [encoding](#encodings) of the input key | ||
The return value will be a `boolean` representing if the hash can be derived from the key | ||
###KDF | ||
`kdf(key, scrypt_parameters, size, salt, callback_function)` | ||
* `key` - [REQUIRED] - an [encoded string or buffer](#encodings) representing the key to be hashed | ||
* `scrypt_parameters` - [REQUIRED] - a JSON object representing the [scrypt's internal parameters](#params) | ||
* `size` - [OPTIONAL] - an integer, representing the size in bytes of the output | ||
* `salt` - [OPTIONAL] - an [encoded string or buffer](#encodings) representing the value used for salt. If not defined, a randome salt will be created. | ||
* `callback_function` - [OPTIONAL] - if present, will make this function asynchronous | ||
The return value will be a JSON object with the following properties: | ||
1. **hash** - the resulting scrypt KDF hash | ||
2. **salt** - the salt used to make the hash | ||
####KDF Config Object | ||
The kdf config object is accessible from `scrypt.kdf.config`. It has the following default value: | ||
{ | ||
saltEncoding: 'buffer', | ||
keyEncoding: 'buffer', | ||
outputEncoding: 'buffer', | ||
defaultSaltSize: 32, | ||
outputSize: 64 | ||
} | ||
* `saltEncoding` - a string representing the [encoding](#encodings) of the input salt | ||
* `keyEncoding` - a string representing the [encoding](#encodings) of the input key | ||
* `outputEncoding` - a string representing the [encoding](#encodings) of the output returned to the user | ||
* `defaultSaltSize` - an integer representing the number of bytes used to create a random salt should it be necessary | ||
* `outputSize` - an integer representing the size of the output in bytes | ||
###Backward Compatibility | ||
####PasswordHash | ||
`passwordHash(key, maxtime, maxmem, maxmemfrac, callback_function)` | ||
* `key` - [REQUIRED] - a key string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. Use 0.1 (100 milliseconds) for interactive logins. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
* `callback_function` - [Optional] - a callback function that will handle processing when result is ready. If this argument is not present, the function will behave in a synchronous manner like the function below. | ||
####PasswordHashSync | ||
`passwordHashSync(key, maxtime, maxmem, maxmemfrac)` | ||
* `key` - [REQUIRED] - a password string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. Use 0.1 (100 milliseconds) for interactive logins. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
####verifyHash | ||
`verifyHash(hash, key, callback_function)` | ||
* `hash` - [REQUIRED] - the password created with the above `passwordHash` function. | ||
* `key` - [REQUIRED] - a password string. | ||
* `callback_function` - [OPTIONAL] - a callback function that will handle processing when result is ready. If this argument is not present, the function will behave in a synchronous manner like the function below | ||
####verifyHashSync | ||
`verifyHashSync(hash, password)` | ||
* `hash` - [REQUIRED] - the password created with the above `passwordHash` function. | ||
* `password` - [REQUIRED] - a password string. | ||
#Example Usage | ||
##params | ||
var scrypt = require("scrypt"); | ||
console.log(scrypt.params.config); //Outputs the config object to screen | ||
//Synchronous | ||
try { | ||
//Uses 0.1 for maxtime, and the values in the config object for maxmem and maxmemfrac | ||
var scryptParameters = scrypt.params(0.1); | ||
console.log(scryptParameters); | ||
} catch(err) { | ||
} | ||
//Asynchronous | ||
scrypt.params(0.1, function(err, scryptParameters) { | ||
console.log(scryptParameters); | ||
}); | ||
##hash | ||
var scrypt = require("scrypt"); | ||
var scryptParameters = scrypt.params(0.1); | ||
var key = new Buffer("this is a key"); //key defaults to buffer in config, so input must be a buffer | ||
//Synchronous example that will output in hexidecimal encoding | ||
scrypt.hash.config.outputEncoding = "hex"; | ||
var hash = scrypt.hash(key, scryptParameters); //should be wrapped in try catch, but leaving it out for brevity | ||
console.log("Synchronous result: "+hash); | ||
//Asynchronous example that expects key to be ascii encoded | ||
scrypt.hash.config.keyEncoding = "ascii"; | ||
scrypt.hash("ascii encoded key", {N: 1, r:1, p:1}, function(err, result){ | ||
//result will be hex encoded | ||
//Note how scrypt parameters was passed as a JSON object | ||
console.log("Asynchronous result: "+hash); | ||
}); | ||
##verify | ||
var scrypt = require("scrypt"); | ||
var scryptParameters = scrypt.params(0.1); | ||
scrypt.hash.config.keyEncoding = "ascii"; | ||
scrypt.verify.config.keyEncoding = "ascii"; | ||
var hash = scrypt.hash("password", scryptParameters); | ||
//Synchronous | ||
scrypt.verify(hash, "password"); //result will be true | ||
scrypt.verify(hash, "incorrect password"); //result will be false | ||
//Asynchronous | ||
scrypt.verify(hash, "password", function(err, result) { | ||
//result will be true | ||
}); | ||
##kdf | ||
The [scrypt paper](http://www.tarsnap.com/scrypt/scrypt.pdf) lists four [test vectors](http://tools.ietf.org/html/draft-josefsson-scrypt-kdf-00#page-11) to test implementation. This example will show how to produce these test vectors from within this module. | ||
####Test Vector 1 | ||
var scrypt = require("scrypt"); | ||
scrypt.kdf.config.saltEncoding = "ascii"; | ||
var key = new Buffer(""); | ||
//Synchronous | ||
var res = scrypt.kdf(key,{"N":16,"r":1,"p":1},64,""); | ||
console.log(res.hash.toString("hex")); | ||
//Asynchronous | ||
scrypt.kdf(key, {"N":16,"r":1,"p":1},64,"", function(err, res) { | ||
console.log(res.hash.toString("hex")); | ||
}); | ||
####Test Vector 2 | ||
var scrypt = require("scrypt"); | ||
scrypt.kdf.config.keyEncoding = "ascii"; | ||
var salt = new Buffer("NaCl"); | ||
//Synchronous | ||
var res = scrypt.kdf("password",{"N":1024,"r":8,"p":16},64,salt); | ||
console.log(res.hash.toString("hex")); | ||
scrypt.kdf("password", {"N":1024,"r":8,"p":16},64,salt, function(err, res) { | ||
console.log(res.hash.toString("hex")); | ||
}); | ||
####Test Vector 3 | ||
var scrypt = require("scrypt"); | ||
scrypt.kdf.config.outputEncoding = "hex"; | ||
var key = new Buffer("pleaseletmein"); | ||
var salt = new Buffer("SodiumChloride"); | ||
//Synchronous | ||
var res = scrypt.kdf(key,{"N":16384,"r":8,"p":1},64,salt); | ||
console.log(res.hash); | ||
//Asynchronous | ||
scrypt.kdf(key, {"N":16384,"r":8,"p":1},64,salt, function(err, res) { | ||
console.log(res.hash); | ||
}); | ||
####Test Vector 4 | ||
Note: This test vector is very taxing in terms of resources. | ||
var scrypt = require("scrypt"); | ||
scrypt.kdf.config.saltEncoding = "ascii"; | ||
scrypt.kdf.config.keyEncoding = "ascii"; | ||
//Synchronous | ||
var res = scrypt.kdf("pleaseletmein",{"N":1048576,"r":8,"p":1},64,"SodiumChloride"); | ||
console.log(res.hash.toString("hex")); | ||
//Asynchronous | ||
scrypt.kdf("pleaseletmein", {"N":1048576,"r":8,"p":1},64,"SodiumChloride", function(err, res) { | ||
console.log(res.hash.toString("hex")); | ||
}); | ||
##Backward Compatibilty Functions | ||
These examples illustrate how to use the backward compatibility functions. | ||
###Asynchronous Authentication And Verification | ||
For interactive authentication, set `maxtime` to `0.1` - 100 milliseconds (although you should ensure that 100 milliseconds on your hardware is sufficiently secure). | ||
####To create a password hash | ||
var scrypt = require("scrypt"); | ||
var password = "This is a password"; | ||
@@ -170,3 +424,2 @@ var maxtime = 0.1; | ||
Note: `maxmem` and `maxmemfrac` can also be passed to hash function. If they are not passed, then `maxmem` defaults to `0` and `maxmemfrac` defaults to `0.5`. If these values are to be passed, then they must be passed after `maxtime` and before the callback function like so: | ||
var scrypt = require("scrypt"); | ||
@@ -183,3 +436,3 @@ var password = "This is a password"; | ||
###To verify a password hash | ||
####To verify a password hash | ||
@@ -197,6 +450,6 @@ var scrypt = require("scrypt"); | ||
##Synchronous Authentication | ||
###Synchronous Authentication And Verification | ||
Again, for interactive authentication, set `maxtime` to `0.1` - 100 milliseconds. | ||
###To create a password hash | ||
####To create a password hash | ||
@@ -210,3 +463,3 @@ var scrypt = require("scrypt"); | ||
Note: `maxmem` and `maxmemfrac` can also be passed to hash function. If they are not passed, then `maxmem` defaults to `0` and `maxmemfrac` defaults to `0.5`. If these values are to be passed, then they must be passed after `maxtime` and before the callback function like so: | ||
var scrypt = require("scrypt"); | ||
@@ -219,3 +472,3 @@ var password = "This is a password"; | ||
###To verify a password hash | ||
####To verify a password hash | ||
@@ -230,121 +483,71 @@ var scrypt = require("scrypt"); | ||
##Asynchronous Encryption and Decryption | ||
## FAQ | ||
### General | ||
#### What Platforms Are Supported? | ||
This module supports most posix platforms. It has been tested on the following platforms: **Linux**, **MAC OS** and **SmartOS** (so its ready for Joyent Cloud). This includes FreeBSD, OpenBSD, SunOS etc. | ||
#### What About Windows? | ||
Windows support is not native to Scrypt, but it does work when using cygwin. With this in mind, I will be updating this module to work on Windows with a prerequisite of cygwin. | ||
### Scrypt | ||
####Why Use Scrypt? | ||
var scrypt = require("scrypt"); | ||
var message = "Hello World"; | ||
var password = "Pass"; | ||
var maxtime = 1.0; | ||
It is probably the most advanced key derivation function available. This is is quote taken from a comment in hacker news: | ||
scrypt.encrypt(message, password, maxtime, function(err, cipher) { | ||
console.log(cipher); | ||
scrypt.decrypt(cipher, password, maxtime, function(err, msg) { | ||
console.log(msg); | ||
}); | ||
}); | ||
>Passwords hashed with scrypt with sufficiently-high strength values (there are 3 tweakable input numbers) are fundamentally impervious to being cracked. I use the word "fundamental" in the literal sense, here; even if you had the resources of a large country, you would not be able to design any hardware (whether it be GPU hardware, custom-designed hardware, or otherwise) which could crack these hashes. Ever. (For sufficiently-small definitions of "ever". At the very least "within your lifetime"; probably far longer.) | ||
Note that `maxmem` and `maxmemfrac` can also be passed to the functions. If they are not passed, then `maxmem` defaults to `0` and `maxmemfrac` defaults to `0.5`. If these values are to be passed, then they must be passed after `maxtime` and before the callback function like so: | ||
var scrypt = require("scrypt"); | ||
var message = "Hello World"; | ||
var password = "Pass"; | ||
var maxtime = 1.0; | ||
var maxmem = 1; //Defaults to 0 if not set | ||
var maxmemfrac = 1.5; //Defaults to 0.5 if not set | ||
#### What Are The Pros And Cons For Using Scrypt | ||
#####Pros | ||
scrypt.encrypt(message, password, maxtime, maxmem, maxmemfrac, function(err, cipher) { | ||
console.log(cipher); | ||
scrypt.decrypt(cipher, password, maxtime, maxmem, maxmemfrac, function(err, msg) { | ||
console.log(msg); | ||
}); | ||
}); | ||
* The Scrypt algorithm has been published by [IETF](http://en.wikipedia.org/wiki/IETF) as an [Internet Draft](http://en.wikipedia.org/wiki/Internet_Draft) and is thus on track to becoming a standard. See [here](https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-00) for the draft. | ||
* It is being actively used in production at [Tarsnap](http://www.tarsnap.com/). | ||
* It is much more secure than bcrypt. | ||
* It is designed to be future proof against attacks with future (and more advanced) hardware. | ||
* It is designed to defend against large scale custom hardware attacks. | ||
* It is production ready. | ||
* There is a Scrypt library for most major scripting languages (Python, Ruby etc). Now this module provides the library for NodeJS :) | ||
##Synchronous Encryption and Decryption | ||
I will end this section with a quote from Colin Percival (author of Scrypt): | ||
var scrypt = require("scrypt"); | ||
var message = "Hello World"; | ||
var password = "Pass"; | ||
var maxtime = 1.0; | ||
> We estimate that on modern (2009) hardware, if 5 seconds are spent computing a derived key, the cost of a hardware brute-force attack against scrypt is roughly 4000 times greater than the cost of a similar attack against bcrypt (to find the same password), and 20000 times greater than a similar attack against PBKDF2. | ||
var cipher = scrypt.encryptSync(message, password, maxtime); | ||
var plainText = scrypt.decryptSync(cipher, password, maxtime); | ||
#####Cons | ||
There is just one con I can think of: It is a relatively new library (only been around since 2009). Cryptographers don't really like new libraries for production deployment as it has not been *battle tested*. That being said, it is being actively used in [Tarsnap](http://www.tarsnap.com/) (as mentioned above) and the author is very active. | ||
Note: that `maxmem` and `maxmemfrac` can also be passed to the functions. If they are not passed, then `maxmem` defaults to `0` and `maxmemfrac` defaults to `0.5`. If these values are to be passed, then they must be passed after `maxtime` and before the callback function like so: | ||
var scrypt = require("scrypt"); | ||
var message = "Hello World"; | ||
var password = "Pass"; | ||
var maxtime = 1.0; | ||
var maxmem = 1; //Defaults to 0 if not set | ||
var maxmemfrac = 1.5; //Defaults to 0.5 if not set | ||
### Hash | ||
#### What Are The Essential Properties For Storing Passwords | ||
Storing passwords requires three essential properties | ||
var cipher = scrypt.encryptSync(message, password, maxtime, maxmem, maxmemfrac); | ||
var plainText = scrypt.decryptSync(cipher, password, maxtime, maxmem, maxmemfrac); | ||
* The password must not be stored in plaintext. (Therefore it is hashed). | ||
* The password hash must be salted. (Making a rainbow table attack very difficult to pull off). | ||
* The salted hash function must not be fast. (If someone does get hold of the salted hashes, their only option will be brute force which will be very slow). | ||
#Api | ||
As an example of how storing passwords can be done badly, take [LinkedIn](http://www.linkedin.com). In 2012, they [came under fire](http://thenextweb.com/socialmedia/2012/06/06/bad-day-for-linkedin-6-5-million-hashed-passwords-reportedly-leaked-change-yours-now/#!rS1HT) for using unsalted hashes to store their passwords. As most commentators at the time were focusing no salt being present, the big picture was missed. In fact, their biggest problem was that they used [sha1](http://en.wikipedia.org/wiki/SHA-1), a very fast hash function. | ||
##Authentication | ||
This module's [hash function](#hash-1) provides all the above properties | ||
###Asynchronous | ||
* `passwordHash(password, maxtime, maxmem, maxmemfrac, callback_function)` | ||
* `password` - [REQUIRED] - a password string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. Use 0.1 (100 milliseconds) for interactive logins. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
* `callback_function` - [REQUIRED] - a callback function that will handle processing when result is ready. | ||
* `verifyHash(hash, password, callback_function)` | ||
* `hash` - [REQUIRED] - the password created with the above `passwordHash` function. | ||
* `password` - [REQUIRED] - a password string. | ||
* `callback_function` - [REQUIRED] - a callback function that will handle processing when result is ready. | ||
#### If random salts are used for each hash, why does each hash start with *c2NyeXB0* when using passwordHash | ||
All hashes start with the word *"scrypt"*. The reason for this is because I am sticking to Colin Percival's (the creator of Scrypt) hash reference implementation whereby he starts off each hash this way. The base64 encoding of the ascii *"scrypt"* is *c2NyeXB0*. Seeing as *passwordHash* defaults it's output to base64, every hash produced will start with *c2NyeXB0*. Next is the Scrypt parameter. Users of Scrypt normally do not change this information once it is settled upon (hence this will also look the same for each hash). | ||
###Synchronous | ||
* `passwordHashSync(password, maxtime, maxmem, maxmemfrac)` | ||
* `password` - [REQUIRED] - a password string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. Use 0.1 (100 milliseconds) for interactive logins. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
* `verifyHashSync(hash, password)` | ||
* `hash` - [REQUIRED] - the password created with the above `passwordHash` function. | ||
* `password` - [REQUIRED] - a password string. | ||
##Encryption/Decryption | ||
To illustrate with an example, I have hashed two password: *password1* and *password2*. Their outputs are as follows: | ||
###Asynchronous | ||
* `encrypt(message, password, maxtime, maxmem, maxmemfrac, callback_function)` | ||
* `message` - [REQUIRED] - the message data to be encrypted. | ||
* `password` - [REQUIRED] - a password string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
* `callback_function` - [REQUIRED] - a callback function that will handle processing when result is ready. | ||
* `decrypt(cipher, password, maxtime, maxmem, maxmemfrac, callback_function)` | ||
* `cipher` - [REQUIRED] - the cipher to be decrypted. | ||
* `password` - [REQUIRED] - a password string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
* `callback_function` - [REQUIRED] - a callback function that will handle processing when result is ready. | ||
password1 | ||
c2NyeXB0AAwAAAAIAAAAAcQ0zwp7QNLklxCn14vB75AYWDIrrT9I/7F9+lVGBfKN/1TH2hs | ||
/HboSy1ptzN0YzHJhC7PZIEPQzf2nuoaqVZg8VkKEJlo8/QaH7qjU2VwB | ||
password2 | ||
c2NyeXB0AAwAAAAIAAAAAZ/+bp8gWcTZgEC7YQZeLLyxFeKRRdDkwbaGeFC0NkdUr/YFAWY | ||
/UwdOH4i/PxW48fXeXBDOTvGWtS3lLUgzNM0PlJbXhMOGd2bke0PvTSnW | ||
###Synchronous | ||
* `encryptSync(message, password, maxtime, maxmem, maxmemfrac)` | ||
* `message` - [REQUIRED] - the message data to be encrypted. | ||
* `password` - [REQUIRED] - a password string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
* `decryptSync(cipher, password, maxtime, maxmem, maxmemfrac)` | ||
* `cipher` - [REQUIRED] - the cipher to be decrypted. | ||
* `password` - [REQUIRED] - a password string. | ||
* `maxtime` - [REQUIRED] - a decimal (double) representing the maxtime in seconds for running Scrypt. | ||
* `maxmem` - [OPTIONAL] - instructs Scrypt to use the specified number of bytes of RAM (default 0). | ||
* `maxmemfrac` - [OPTIONAL] - instructs Scrypt to use the specified fracion of RAM (defaults 0.5). | ||
As one can see from the above example, both hashes start off by looking similar (they both start with *c2NyeXB0AAwAAAAIAAAAA* - as explained above), but afterwards, things change very rapidly. In fact, I hashed the password *password1* again: | ||
#Credits | ||
password1 | ||
c2NyeXB0AAwAAAAIAAAAATpP+fdQAryDiRmCmcoOrZa2mZ049KdbA/ofTTrATQQ+m | ||
0L/gR811d0WQyip6p2skXVEMz2+8U+xGryFu2p0yzfCxYLUrAaIzaZELkN2M6k0 | ||
Compare this hash to the one above. Even though they start off looking similar, their outputs are vastly different (even though it is the same password being hashed). This is because of the **random** salt that has been added, ensuring that no two hashes will ever be indentical, even if the password that is being hashed is the same. | ||
For those that are curious or paranoid, please look at how the hash is both [produced](https://github.com/barrysteyn/node-scrypt/blob/master/src/scryptwrapper/hash.c#L37-81) and [verified](https://github.com/barrysteyn/node-scrypt/blob/master/src/scryptwrapper/hash.c#L83-122) (you are going to need some knowledge of the [C language](http://c.learncodethehardway.org/book/) for this). | ||
##Credits | ||
The Scrypt library is Colin Percival's [Scrypt](http://www.tarsnap.com/scrypt.html) project. This includes the encryption/decryption functions which are basically just wrappers into this library. | ||
The password hash and verify functions are also very heavily influenced by the Scrypt source code, with most functionality being copied from various placed within Scrypt. | ||
#Contributors | ||
* [René Nyffenegger](http://www.adp-gmbh.ch/) - produced original Base64 encoding code. | ||
* [Kelvin Wong](https://github.com/kelvinwong-ca) - MAC OS compilation and testing. | ||
* [Tamas Geschitz](https://github.com/gtamas) - SmartOS and MAC OS testing |
var test = require('tap').test; | ||
var scrypt = require('../build/Release/scrypt'); | ||
var password = "This is the test password"; | ||
var maxtime_passwordhash = 0.05; | ||
var maxtime_crypto = 0.05; | ||
var scrypt = require('../'); | ||
var keyString = "This is the test key"; | ||
var keyStringObject = new String(keyString); | ||
var keyBuffer = new Buffer(keyString); | ||
var message = "This is a message"; | ||
var scryptParameters = {"N":1, "r":1, "p":1} | ||
//Key Derivation Tests | ||
test("Asynchronous: Password hashing with incorrect arguments - only two arguments present", function(t) { | ||
console.log("Password Hash Functionality\nTesting of arguments\n"); | ||
try { | ||
scrypt.passwordHash(maxtime_passwordhash, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either password, max_time or callback not present - in this case, password was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
//These error results are taken verbatim from src/node-boilerplate/scrypt_common.h | ||
var JSARG=1; //Error in JavaScript land: Argument mismatch | ||
var ADDONARG=2; //Error resulting from argument mismatch in the node addon module | ||
var PARMOBJ=3; //Scrypt generated errors | ||
var SCRYPT=4; //Scrypt generated errors | ||
/* | ||
* Logic Tests | ||
*/ | ||
test("KDF - Test vector 1", function(t) { | ||
var kdf = scrypt.KDF(); | ||
kdf.config.saltEncoding = "ascii"; | ||
var buf = new Buffer(""); | ||
var res = kdf(buf,{"N":16,"r":1,"p":1},64,""); | ||
t.equal(res.hash.toString("hex"),"77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906","Synchronous test: first test vector is correctly returned"); | ||
kdf(buf, {"N":16,"r":1,"p":1},64,"", function(err, res) { | ||
t.equal(res.hash.toString("hex"),"77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906","Asynchronous test: first test vector is correctly returned"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Password hashing with incorrect arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.passwordHash(password, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either password, max_time or callback not present - in this case, maxtime_passwordhash was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("KDF - Test vector 2", function(t) { | ||
var kdf = scrypt.KDF(); | ||
kdf.config.keyEncoding = "ascii"; | ||
var buf = new Buffer("NaCl"); | ||
var res = kdf("password",{"N":1024,"r":8,"p":16},64,buf); | ||
t.equal(res.hash.toString("hex"),"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640","Synchronous test: second test vector is correctly returned"); | ||
kdf("password", {"N":1024,"r":8,"p":16},64,buf, function(err, res) { | ||
t.equal(res.hash.toString("hex"),"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640","Synchronous test: second test vector is correctly returned"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Password hashing with incorrect arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.passwordHash(password, maxtime_passwordhash); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either password, max_time or callback not present - in this case, callback was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("KDF - Test vector 3", function(t) { | ||
var kdf = scrypt.KDF(); | ||
kdf.config.outputEncoding = "hex"; | ||
var buf = new Buffer("pleaseletmein"); | ||
var salt = new Buffer("SodiumChloride"); | ||
var res = kdf(buf,{"N":16384,"r":8,"p":1},64,salt); | ||
t.equal(res.hash, "7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887","Synchronous test: third test vector is correctly returned"); | ||
kdf(buf, {"N":16384,"r":8,"p":1},64,salt, function(err, res) { | ||
t.equal(res.hash,"7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2d5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887","Asynchronous test: third test vector is correctly returned"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Password hashing with incorrect arguments - password given an argument that is not a string", function(t) { | ||
try { | ||
scrypt.passwordHash(1232, maxtime_passwordhash, function(err, hash) { | ||
}) | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because password was not set as a string (it was set as 1232)"); | ||
t.equal(err.message,"password must be a string", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
/*test("KDF - Test vector 4", function(t) { //This test takes too long to perform for continuous integration | ||
var res = scrypt.kdf("pleaseletmein",{"N":1048576,"r":8,"p":1},64,"SodiumChloride"); | ||
t.equal(res.hash.toString("hex"),"2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4", "Synchronous test: fourth test vector is correctly returned"); | ||
scrypt.kdf("pleaseletmein", {"N":1048576,"r":8,"p":1},64,"SodiumChloride", function(err, res) { | ||
t.equal(res.hash.toString("hex"),"2101cb9b6a511aaeaddbbe09cf70f881ec568d574a2ffd4dabe5ee9820adaa478e56fd8f4ba5d09ffa1c6d927c40f4c337304049e8a952fbcbf45c6fa77a41a4","Asynchronous test: fourth test vector is correctly returned"); | ||
t.end(); | ||
}); | ||
});*/ | ||
test("KDF - Random salt added by default", function(t) { | ||
var key = new Buffer("key"); | ||
var hash1 = scrypt.kdf(key, scryptParameters); | ||
var hash2 = scrypt.kdf(key, scryptParameters); | ||
t.notEqual(hash1.hash.toString(), hash2.hash.toString(), "Synchronous: hashes that are returned are not equal. This is correct due to random salt that was added"); | ||
t.notEqual(hash1.salt.toString(), hash2.salt.toString(), "Synchronous: salts that are returned are not equal"); | ||
scrypt.kdf(key, scryptParameters, function(err, hash1) { | ||
scrypt.kdf(key, scryptParameters, function(err, hash2) { | ||
t.notEqual(hash1.hash.toString(), hash2.hash.toString(), "Asynchronous: hashes that are returned are not equal. This is correct due to random salt that was added"); | ||
t.notEqual(hash1.salt.toString(), hash2.salt.toString(), "Asynchronous: salts that are returned are not equal"); | ||
t.end(); | ||
}); | ||
}); | ||
}); | ||
test("Asynchronous: Password hashing with incorrect arguments - maxtime given an argument that is not a number", function(t) { | ||
try { | ||
scrypt.passwordHash(password, 'a', function(err, hash) { | ||
}) | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because maxtime was not set as a number (it was set as 'a')"); | ||
t.equal(err.message,"maxtime argument must be a number", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("KDF - Deterministic non-random salt added manually", function(t) { | ||
var key = new Buffer("key"); | ||
var salt = new Buffer("salt"); | ||
var hash1 = scrypt.kdf(key, scryptParameters, 64, salt); | ||
var hash2 = scrypt.kdf(key, scryptParameters, 64, salt); | ||
t.equal(hash1.hash.toString(), hash2.hash.toString(), "Synchronous: hashes that are returned are equal. This is correct due to non-random salt that was added"); | ||
t.equal(hash1.salt.toString(), hash2.salt.toString(), "Synchronous: salts that are returned are identical"); | ||
scrypt.kdf(key, scryptParameters, 64, salt, function(err, hash1) { | ||
scrypt.kdf(key, scryptParameters, 64, salt, function(err, hash2) { | ||
t.equal(hash1.hash.toString(), hash2.hash.toString(), "Asynchronous: hashes that are returned are equal. This is correct due to non-random salt that was added"); | ||
t.equal(hash1.salt.toString(), hash2.salt.toString(), "Asynchronous: salts that are returned are identical"); | ||
t.end(); | ||
}); | ||
}); | ||
}); | ||
test("Asynchronous: Password hashing with incorrect arguments - no callback function present", function(t) { | ||
try { | ||
scrypt.passwordHash(password, maxtime_passwordhash, 1); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown there was no callback function present"); | ||
t.equal(err.message,"callback function not present", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Password hashing: Salt means same keys hash to different values", function(t) { | ||
var hash1 = scrypt.passwordHashSync(keyString, scryptParameters); | ||
var hash2 = scrypt.passwordHashSync(keyString, scryptParameters); | ||
t.notEqual(hash1,hash2,"Synchronous: same keys are correctly hashed to different values due to salt"); | ||
scrypt.passwordHash(keyString, scryptParameters, function(err, hash1) { | ||
scrypt.passwordHash(keyString, scryptParameters, function(err, hash2) { | ||
t.notEqual(hash1,hash2,"Asynchronous: same keys are correctly hashed to different values due to salt"); | ||
t.end(); | ||
}); | ||
}); | ||
}); | ||
test("Asynchronous: Password hashing and verifying: Same password verify and hash (Result Must Be True)", function(t) { | ||
console.log("\nPassword Hash Functionality\nTesting of hashing functionality\n"); | ||
scrypt.passwordHash(password, maxtime_passwordhash, function(err, hash) { | ||
t.notOk(err,'No error hashing password'); | ||
scrypt.verifyHash(hash, password, function(err, result) { | ||
t.notOk(err,'No error verifying hash'); | ||
t.equal(result, true,'Hash has been verified as true => Result Is True'); | ||
test("Password hashing and verifying: Same key verify and hash (Consistency test - Result Must Be True)", function(t) { | ||
hash = scrypt.passwordHash(keyString, scryptParameters); | ||
result = scrypt.verifyHash(hash, keyString); | ||
t.equal(result, true,"Synchronous: hash has been verified as true => Result Is True"); | ||
scrypt.passwordHash(keyString, scryptParameters, function(err, hash) { | ||
t.notOk(err,"Asynchronous: no error hashing result"); | ||
scrypt.verifyHash(hash, keyString, function(err, result) { | ||
t.notOk(err,"Asynchronous: no error verifying hash"); | ||
t.equal(result, true,"Asynchronous: hash has been verified as true => Result Is True"); | ||
t.end(); | ||
@@ -84,8 +130,12 @@ }) | ||
test("Asynchronous: Password hashing and verifying: Different password verify and hash (Result Must Be False)", function(t) { | ||
scrypt.passwordHash(password, maxtime_passwordhash, function(err, hash) { | ||
t.notOk(err,'No error hashing password'); | ||
scrypt.verifyHash(hash, "Another password", function(err, result) { | ||
t.ok(err,'Verification of hash failed because different passwords used'); | ||
t.equal(result, false,'Hash has not been verified => Result Is False'); | ||
test("Password hashing and verifying: Different keys do not verify (Result Must Be False)", function(t) { | ||
hash = scrypt.passwordHash(keyString, scryptParameters); | ||
result = scrypt.verifyHash(hash, "another key"); | ||
t.equal(result, false,"Synchronous: hash has been verified as false => Result Is False (as it should be)"); | ||
scrypt.passwordHash(keyString, scryptParameters, function(err, hash) { | ||
t.notOk(err,"Asynchronous: no error hashing result"); | ||
scrypt.verifyHash(hash, "another key", function(err, result) { | ||
t.ok(err,"Asynchronous: error verifying hash - because hashes are not the same (as expected)"); | ||
t.equal(result, false,"Asynchronous: hash has been verified as false => Result Is False (as it should be)"); | ||
t.end(); | ||
@@ -96,293 +146,789 @@ }) | ||
test("Asynchronous: Password hashing: Salt means same passwords hash to different values", function(t) { | ||
scrypt.passwordHash(password, maxtime_passwordhash, function(err, hash1) { | ||
scrypt.passwordHash(password, maxtime_passwordhash, function(err, hash2) { | ||
t.notEqual(hash1,hash2,"Same passwords are correctly hashed to different values due to salt"); | ||
t.end(); | ||
}) | ||
}) | ||
/* | ||
* Argument Tests | ||
*/ | ||
// | ||
// Translation Function (Parameter) Tests */ | ||
// | ||
//General (applies to both async and sync) | ||
test("Pick Parameters (Translation function): - no arguments are present", function(t) { | ||
try { | ||
scrypt.params(); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because at one least argument is needed - in this case, no arguments were given"); | ||
t.deepEqual(err, scrypt.errorObject(ADDONARG, "at least one argument is needed - the maxtime"), "The correct message object is returned, namely:"+ JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
//Crypto Tests | ||
test("Pick Parameters (Translation function): incorrect argument type", function(t) { | ||
try { | ||
scrypt.params("abc"); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because an incorrect type was passed to the function - in this case, the maxtime was passed as a string, but a number is expected"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"maxtime argument must be a number"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Asynchronous: Encryption With Incorrect Arguments - only three arguments present", function(t) { | ||
console.log("\nEncryption/Decryption\nTesting Encryption of arguments\n"); | ||
try { | ||
scrypt.encrypt(password, maxtime_crypto, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, message was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Pick Parameters (Translation function): incorrect argument type", function(t) { | ||
try { | ||
scrypt.params(0); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because maxtime was passed as a number <= 0"); | ||
t.deepEqual(err, scrypt.errorObject(ADDONARG, "maxtime must be greater than 0"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Asynchronous: Encryption With Incorrect Arguments - only three arguments present", function(t) { | ||
try { | ||
scrypt.encrypt(message, maxtime_crypto, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, password was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Pick Parameters (Translation function): incorrect argument type", function(t) { | ||
try { | ||
scrypt.params(1, "abc"); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because an incorrect type was passed to the function - in this case, the maxmem was passed as a string, but a number is expected"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG, "maxmem argument must be a number"), "The correct object is returned, namely: "+ JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Asynchronous: Encryption With Incorrect Arguments - only three arguments present", function(t) { | ||
try { | ||
scrypt.encrypt(message, password, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, maxtime was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Pick Parameters (Translation function): incorrect argument type", function(t) { | ||
try { | ||
scrypt.params(1, 0.5, "abc"); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because an incorrect type was passed to the function - in this case, the maxmemfrac was passed as a string, but a number is expected"); | ||
t.deepEqual(err, scrypt.errorObject(ADDONARG, "max_memfrac argument must be a number"), "The correct object is returned, namely: " + JSON.stringify(err.message)); | ||
t.end(); | ||
} | ||
}); | ||
test("Asynchronous: Encryption With Incorrect Arguments - only three arguments present", function(t) { | ||
try { | ||
scrypt.encrypt(message, password, maxtime_crypto ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, callback function was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
//Asynchronous | ||
test("Pick Parameters (Asynchronous): incorrect argument - no arguments before callback", function(t) { | ||
try { | ||
scrypt.params(function(){}); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because at one least argument is needed before the callback - in this case, no arguments were given"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"at least one argument is needed before the callback - the maxtime"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Asynchronous: Decryption With Incorrect Arguments - only three arguments present", function(t) { | ||
try { | ||
scrypt.decrypt(password, maxtime_crypto, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, message was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Asynchronous: Pick parameters returns an object given correct inputs of just maxtime", function(t) { | ||
scrypt.params(2, function(err, parameters) { | ||
t.type(parameters,"object","Returned entity is an object"); | ||
t.type(parameters.N, "number","N is present in object and is of type number"); | ||
t.type(parameters.r, "number","r is present in object and is of type number"); | ||
t.type(parameters.p, "number","p is present in object and is of type number"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Decryption With Incorrect Arguments - only three arguments present", function(t) { | ||
try { | ||
scrypt.decrypt(message, maxtime_crypto, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, password was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Asynchronous: Pick parameters returns an object given correct inputs of just maxtime and max_memfrac", function(t) { | ||
scrypt.params(2, 0.5, function(err, parameters) { | ||
t.type(parameters,"object","Returned entity is an object"); | ||
t.type(parameters.N, "number","N is present in object and is of type number"); | ||
t.type(parameters.r, "number","r is present in object and is of type number"); | ||
t.type(parameters.p, "number","p is present in object and is of type number"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Decryption With Incorrect Arguments - only three arguments present", function(t) { | ||
try { | ||
scrypt.decrypt(message, password, function(err, hash) {} ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, maxtime was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Asynchronous: Pick parameters returns an object given correct inputs of maxtime, max_memfrac and maxmem", function(t) { | ||
scrypt.params(2, 0.5, 1, function(err, parameters) { | ||
t.type(parameters,"object","Returned entity is an object"); | ||
t.type(parameters.N, "number","N is present in object and is of type number"); | ||
t.type(parameters.r, "number","r is present in object and is of type number"); | ||
t.type(parameters.p, "number","p is present in object and is of type number"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Decryption With Incorrect Arguments - only three arguments present", function(t) { | ||
try { | ||
scrypt.decrypt(message, password, maxtime_crypto ); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time or callback not present - in this case, callback function was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least four arguments are needed - data, password, max_time and a callback function", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
//Synchronous | ||
test("Synchronous: Pick parameters returns an object given correct inputs of just maxtime", function(t) { | ||
parameters = scrypt.params(2); | ||
t.type(parameters,"object","Returned entity is an object"); | ||
t.type(parameters.N, "number","N is present in object and is of type number"); | ||
t.type(parameters.r, "number","r is present in object and is of type number"); | ||
t.type(parameters.p, "number","p is present in object and is of type number"); | ||
t.end(); | ||
}); | ||
test("Asynchronous: Encryption/Decryption - Encrypting a message (This test will take "+maxtime_crypto+" seconds)", function(t) { | ||
scrypt.encrypt(message, password, maxtime_crypto, function(err, cipher) { | ||
t.notOk(err,'No error producing cipher'); | ||
test("Synchronous: Pick parameters returns an object given correct inputs of just maxtime and max_memfrac", function(t) { | ||
parameters = scrypt.params(2, 0.5); | ||
t.type(parameters,"object","Returned entity is an object"); | ||
t.type(parameters.N, "number","N is present in object and is of type number"); | ||
t.type(parameters.r, "number","r is present in object and is of type number"); | ||
t.type(parameters.p, "number","p is present in object and is of type number"); | ||
t.end(); | ||
}); | ||
test("Synchronous: Pick parameters returns an object given correct inputs of maxtime, max_memfrac and maxmem", function(t) { | ||
parameters = scrypt.params(2, 0.5, 1); | ||
t.type(parameters,"object","Returned entity is an object"); | ||
t.type(parameters.N, "number","N is present in object and is of type number"); | ||
t.type(parameters.r, "number","r is present in object and is of type number"); | ||
t.type(parameters.p, "number","p is present in object and is of type number"); | ||
t.end(); | ||
}); | ||
// | ||
// Password Hash Tests | ||
// | ||
//General (both async and sync) | ||
test("Password Hash: incorrect arguments - no arguments present", function(t) { | ||
try { | ||
scrypt.passwordHash(); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because at one least two arguments are needed - in this case, no arguments were given"); | ||
t.deepEqual(err,scrypt.errorObject(JSARG,"wrong number of arguments - at least two arguments are needed - key and scrypt parameters JSON object"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - only one argument present", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because at one least two arguments are needed - in this case, only one was present, namely the key"); | ||
t.deepEqual(err,scrypt.errorObject(JSARG,"wrong number of arguments - at least two arguments are needed - key and scrypt parameters JSON object"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected key is not a string", function(t) { | ||
try { | ||
scrypt.passwordHash(1232, scryptParameters); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: An error was correctly thrown because the key type was incorrect - in this case, it was of type number, but it should be of type string or buffer"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(1232, scryptParameters, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: An error was correctly thrown because the key type was incorrect - in this case, it was of type number, but it should be of type string or buffer"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected scrypt parameter object is malformed", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, {}); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, it is an empty object"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"N value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, {}, function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, it is an empty object"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"N value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected scrypt parameter object is malformed", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1}); | ||
} catch (err) { | ||
t.ok(err, "Synchronout test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, it has one property, only N (but r and p are also needed)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"r value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1}, function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asychronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, it has one property, only N (but r and p are also needed)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"r value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected scrypt parameter object is malformed", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"r":1}); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because the scrypt parameter object is malformed - in this case, it has two properties, only N and r (but p is also needed)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"p value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"r":1}, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asyncrhonous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, it has two properties, only N and r (but p is also needed)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"p value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected scrypt parameter object is malformed", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"p":1}); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, it has two properties, only N and p (but r is also needed)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"r value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"p":1}, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, it has two properties, only N and p (but r is also needed)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"r value is not present"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected scrypt parameter object is malformed", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, {"N":"hello","r":1, "p":1}); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, N type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"N must be a numeric value"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, {"N":"hello","r":1, "p":1},function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, N type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"N must be a numeric value"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected scrypt parameter object is malformed", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"r":"hello", "p":1}); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, r type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"r must be a numeric value"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"r":"hello", "p":1}, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, r type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"r must be a numeric value"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - expected scrypt parameter object is malformed", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"r":1, "p":"hello"}); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, p type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"p must be a numeric value"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, {"N":1,"r":1, "p":"hello"}, function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, p type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(PARMOBJ,"p must be a numeric value"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - maxtime not a number", function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, "hello world"); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, p type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(JSARG,"expecting maxtime as a number"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(keyString, "hello world", function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the scrypt parameter object is malformed - in this case, p type is a string (it should be a numeric)"); | ||
t.deepEqual(err,scrypt.errorObject(JSARG,"expecting maxtime as a number"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - key string is empty", function(t) { | ||
try { | ||
scrypt.passwordHash("", scryptParameters); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the key string was empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash("", scryptParameters, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the key string was empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - key string object is empty", function(t) { | ||
try { | ||
scrypt.passwordHash(new String(""), scryptParameters); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test: an error was correctly thrown because the key string was empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(new String(""), scryptParameters, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the key string was empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - key buffer is empty", function(t) { | ||
try { | ||
scrypt.passwordHash(new Buffer(""), scryptParameters); | ||
} catch (err) { | ||
t.ok(err, "synchronous test: an error was correctly thrown because the key string was empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.passwordHash(new Buffer(""), scryptParameters, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test: an error was correctly thrown because the key string was empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Hash: incorrect arguments - only two arguments present, key and callback function" , function(t) { | ||
try { | ||
scrypt.passwordHash(keyString, function(){}); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because there was no scrypt parameters object"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"wrong number of arguments at least two arguments are needed before the callback function - key and scrypt parameters JSON object"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
//Synchronous | ||
test("Password Hash (Synchronous): hash key with correct arguments: key string and scrypt parameters object", function(t) { | ||
var hash = scrypt.passwordHash(keyString, scryptParameters); | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
test("Password Hash (Synchronous): hash key with correct arguments: key string and maxtime number", function(t) { | ||
var hash = scrypt.passwordHash(keyString, 1); | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
test("Password Hash (Synchronous): hash key with correct arguments: key string, maxtime number and maxmem number", function(t) { | ||
var hash = scrypt.passwordHash(keyString, 1, 0.05); | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
test("Password Hash (Synchronous): hash key with correct arguments: key string,maxtime number, maxmemnumber and maxmem_frac number", function(t) { | ||
var hash = scrypt.passwordHash(keyString, 1, 0.05, 0.05); | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
test("Password Hash (Synchronous): hash key with correct arguments: key string object and scrypt parameters object", function(t) { | ||
var hash = scrypt.passwordHash(keyStringObject, scryptParameters); | ||
t.ok(true, "The key was hashed successfully with a string object, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
test("Password Hash (Synchronous): hash key with correct arguments: key buffer and scrypt parameters object", function(t) { | ||
var hash = scrypt.passwordHash(keyBuffer, scryptParameters); | ||
t.ok(true, "The key was hashed successfully with a buffer, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
//Asynchronous | ||
test("Password Hash (Asynchronous): hash key with correct arguments: key string and scrypt parameters object", function(t) { | ||
scrypt.passwordHash(keyString, scryptParameters, function(err, hash) { | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Encryption/Decryption - Decrypting a message results in the same message used as input to encryption (Testing consistency property of cryptography)", function(t) { | ||
scrypt.encrypt(message, password, maxtime_crypto, function(err, cipher) { | ||
scrypt.decrypt(cipher, password, maxtime_crypto, function(err, decipher_message) { | ||
t.notOk(err,'No error decrypting'); | ||
t.equal(message, decipher_message,"Consistency property is working as expected"); | ||
t.end(); | ||
}) | ||
test("Password Hash (Asynchronous): hash key with correct arguments: key string and maxtime number", function(t) { | ||
scrypt.passwordHash(keyString, 1, function(err, hash) { | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Asynchronous: Encryption/Decryption - Decrypting does not work if given incorrect password", function(t) { | ||
scrypt.encrypt(message, password, maxtime_crypto, function(err, cipher) { | ||
scrypt.decrypt(cipher, password+'rubbish', maxtime_crypto, function(err, decipher_message) { | ||
t.ok(err,'An error was correctly generated due to an incorrect password'); | ||
t.end(); | ||
}) | ||
test("Password Hash (Aynchronous): hash key with correct arguments: key string, maxtime number and maxmem number", function(t) { | ||
scrypt.passwordHash(keyString, 1, 0.05, function(err, hash) { | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
}); | ||
//Key Derivation Tests | ||
test("Synchronous: Password hashing with incorrect arguments - only two arguments present", function(t) { | ||
console.log("Password Hash Functionality\nTesting of arguments\n"); | ||
try { | ||
scrypt.passwordHashSync(maxtime_passwordhash); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either password or max_time not present - in this case, password was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least two arguments are needed - password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Password Hash (Asynchronous): hash key with correct arguments: key string,maxtime number, maxmemnumber and maxmem_frac number", function(t) { | ||
scrypt.passwordHash(keyString, 1, 0.05, 0.05, function(err, hash) { | ||
t.ok(true, "The key was hashed successfully, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Synchronous: Password hashing with incorrect arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.passwordHashSync(password); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either password, max_time not present - in this case, maxtime was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least two arguments are needed - password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Password Hash (Asynchronous): hash key with correct arguments: key string object and scrypt parameters object", function(t) { | ||
scrypt.passwordHash(keyStringObject, scryptParameters, function(err, hash) { | ||
t.ok(true, "The key was hashed successfully with a string object, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Password Hash (Asynchronous): hash key with correct arguments: key buffer and scrypt parameters object", function(t) { | ||
scrypt.passwordHash(keyBuffer, scryptParameters,function (err, hash) { | ||
t.ok(true, "The key was hashed successfully with a buffer, as expected"); | ||
t.type(hash, "string", "The hash that was returned is of type 'string', as expected (because it is base64 encoded)"); | ||
t.end(); | ||
}); | ||
}); | ||
test("Synchronous: Password hashing with incorrect arguments - password given an argument that is not a string", function(t) { | ||
try { | ||
scrypt.passwordHashSync(1232, maxtime_passwordhash); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because password was not set as a string (it was set as 1232)"); | ||
t.equal(err.message,"password must be a string", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
// | ||
// Password Verify | ||
// | ||
test("Password Verify: incorrect arguments - no arguments present", function(t) { | ||
try { | ||
scrypt.verifyHash(); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because at one least two arguments are needed - in this case, no arguments were given"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"both hash and key are needed"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Password hashing with incorrect arguments - maxtime given an argument that is not a number", function(t) { | ||
try { | ||
scrypt.passwordHashSync(password, 'a'); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because maxtime was not set as a number (it was set as 'a')"); | ||
t.equal(err.message,"maxtime argument must be a number", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Password Verify: incorrect arguments - one argument present", function(t) { | ||
try { | ||
scrypt.verifyHash("hash"); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because at one least two arguments are needed - in this case, no arguments were given"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"both hash and key are needed"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Password hashing and verifying: Same password verify and hash (Result Must Be True)", function(t) { | ||
console.log("\nPassword Hash Functionality\nTesting of hashing functionality\n"); | ||
var hash = scrypt.passwordHashSync(password, maxtime_passwordhash); | ||
var result = scrypt.verifyHashSync(hash, password); | ||
t.equal(result, true,'Hash has been verified as true => Result Is True'); | ||
t.end(); | ||
test("Password Verify: incorrect argument type - two arguments present, but one of them is a callback", function(t) { | ||
try { | ||
scrypt.verifyHash("hash", function(){}); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because at one least two arguments are needed - in this case, no arguments were given"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"both hash and key are needed before the callback function"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Password hashing and verifying: Different password verify and hash (Result Must Be False)", function(t) { | ||
var hash = scrypt.passwordHashSync(password, maxtime_passwordhash); | ||
var result = scrypt.verifyHashSync(hash, "Another password"); | ||
t.equal(result, false,'Hash has not been verified => Result Is False'); | ||
t.end(); | ||
test("Password Verify: incorrect argument type - hash not a string nor a string object nor a buffer", function(t) { | ||
try { | ||
scrypt.verifyHash(123, "string"); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because hash is not a recognised type, namely string, string object or buffer. In this case, hash is a number"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.verifyHash(123, "string", function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because hash is not a recognised type, namely string, string object or buffer. In this case, hash is a number"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Password hashing: Salt means same passwords hash to different values", function(t) { | ||
var hash1 = scrypt.passwordHashSync(password, maxtime_passwordhash); | ||
var hash2 = scrypt.passwordHashSync(password, maxtime_passwordhash); | ||
t.notEqual(hash1,hash2,"Same passwords are correctly hashed to different values due to salt"); | ||
t.end(); | ||
test("Password Verify: incorrect argument type - hash is an object, but not a buffer nor a string object", function(t) { | ||
try { | ||
scrypt.verifyHash({}, "string"); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because hash is not a recognised type, namely string, string object or buffer. In this case, hash is an empty JSON object"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.verifyHash({}, "string", function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because hash is not a recognised type, namely string, string object or buffer. In this case, hash is an empty JSON object"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
//Crypto Tests | ||
test("Password Verify: incorrect argument type - hash an empty string", function(t) { | ||
try { | ||
scrypt.verifyHash("", keyString); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the hash string is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
test("Synchronous: Encryption With Incorrect Arguments - only two arguments present", function(t) { | ||
console.log("\nEncryption/Decryption\nTesting Encryption of arguments\n"); | ||
try { | ||
scrypt.encryptSync(password, maxtime_crypto); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password or max_time was not present - in this case, message was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - data, password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
try { | ||
scrypt.verifyHash("", "string", function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the hash string is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Encryption With Incorrect Arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.encryptSync(message, maxtime_crypto); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password or max_time was not present - in this case, password was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - data, password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Password Verify: incorrect argument type - hash an empty string object", function(t) { | ||
try { | ||
scrypt.verifyHash(new String(""), keyString); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the hash string object is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.verifyHash(new String(""), "string", function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the hash string object is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Encryption With Incorrect Arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.encryptSync(message, password); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password or max_time not present - in this case, maxtime was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - data, password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Password Verify: incorrect argument type - hash an empty buffer", function(t) { | ||
try { | ||
scrypt.verifyHash(new Buffer(""), "string"); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the hash buffer is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.verifyHash(new Buffer(""), "string", function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the hash buffer is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"hash cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Verify: incorrect argument type - key not a string nor a string object nor a buffer", function(t) { | ||
try { | ||
scrypt.verifyHash("hash", 123); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because key is not a recognised type, namely string, string object or buffer. In this case, key is a number"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
test("Synchronous: Decryption With Incorrect Arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.decryptSync(password, maxtime_crypto); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password or max_time was not present - in this case, message was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - data, password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
try { | ||
scrypt.verifyHash("hash", 123, function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because key is not a recognised type, namely string, string object or buffer. In this case, key is a number"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Decryption With Incorrect Arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.decryptSync(message, maxtime_crypto); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password, max_time was not present - in this case, password was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - data, password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
test("Password Verify: incorrect argument type - key is an object, but not a buffer nor a string object", function(t) { | ||
try { | ||
scrypt.verifyHash("hash",{}); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because key is not a recognised type, namely string, string object or buffer. In this case, key is an empty JSON object"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.verifyHash("hash", {}, function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because hash is not a recognised type, namely string, string object or buffer. In this case, key is an empty JSON object"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Verify: incorrect argument type - key an empty string", function(t) { | ||
try { | ||
scrypt.verifyHash("hash", ""); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the key string is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
test("Synchronous: Decryption With Incorrect Arguments - only two arguments present", function(t) { | ||
try { | ||
scrypt.decryptSync(message, password); | ||
} catch (err) { | ||
t.ok(err,"An error was correctly thrown because either message, password or max_time was not present - in this case, maxtime was not present"); | ||
t.equal(err.message,"Wrong number of arguments: At least three arguments are needed - data, password and max_time", "The correct message is displayed, namely: "+err.message); | ||
t.end(); | ||
} | ||
try { | ||
scrypt.verifyHash("hash", "", function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the key string is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Encryption/Decryption - Encrypting a message (This test will take "+maxtime_crypto+" seconds)", function(t) { | ||
var error = null; | ||
try { | ||
scrypt.encryptSync(message, password, maxtime_crypto); | ||
} catch(err) { | ||
error = err.message; | ||
} | ||
t.notOk(error,'No error producing cipher'); | ||
t.end(); | ||
test("Password Verify: incorrect argument type - key an empty string object", function(t) { | ||
try { | ||
scrypt.verifyHash("hash", new String("")); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the key string object is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.verifyHash("hash", new String(""), function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the key string object is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Password Verify: incorrect argument type - key an empty buffer", function(t) { | ||
try { | ||
scrypt.verifyHash("hash", new Buffer("")); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the key buffer is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
test("Synchronous: Encryption/Decryption - Decrypting a message results in the same message used as input to encryption (Testing consistency property of cryptography)", function(t) { | ||
var error = null; | ||
var cipher = ''; | ||
var plaintext = ''; | ||
try { | ||
cipher = scrypt.encryptSync(message, password, maxtime_crypto); | ||
plaintext = scrypt.decryptSync(cipher, password, maxtime_crypto); | ||
} catch(err) { | ||
error = err.message; | ||
} | ||
try { | ||
scrypt.verifyHash("hash", new Buffer(""), function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the key buffer is empty"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key cannot be empty"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
t.notOk(error,'No error decrypting'); | ||
t.equal(message, plaintext,"Consistency property is working as expected"); | ||
t.end(); | ||
// | ||
// Scrypt KDF Tests | ||
// | ||
test("Scrypt KDF: Incorrect arguments - no arguments present", function(t) { | ||
try { | ||
scrypt.kdf(); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because there were no arguments present"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"at least two arguments are needed - key and a json object representing the scrypt parameters"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Synchronous: Encryption/Decryption - Decrypting does not work if given incorrect password", function(t) { | ||
var error = null; | ||
var cipher = ''; | ||
var plaintext = ''; | ||
try { | ||
cipher = scrypt.encryptSync(message, password, maxtime_crypto); | ||
plaintext = scrypt.decryptSync(cipher, password+'rubbish', maxtime_crypto); | ||
} catch(err) { | ||
error = err.message; | ||
} | ||
test("Scrypt KDF: Incorrect arguments - callback in incorrect position", function(t) { | ||
try { | ||
scrypt.kdf("string",function(){}); | ||
} catch (err) { | ||
t.ok(err, "An error was correctly thrown because the callback function preceeded other needed arguments"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"at least two arguments are needed before the callback function - key and a json object representing the scrypt parameters"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
t.ok(error,'An error was correctly generated due to an incorrect password'); | ||
t.end(); | ||
test("Scrypt KDF: Incorrect arguments - key is not of type string, string object nor buffer", function(t) { | ||
try { | ||
scrypt.kdf(123, scryptParameters); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the key is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.kdf(123, scryptParameters, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the key is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Scrypt KDF: Incorrect arguments - key is an object, not a string object nor a buffer", function(t) { | ||
try { | ||
scrypt.kdf({}, scryptParameters); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the key is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
scrypt.kdf({}, scryptParameters, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the key is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"key must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Scrypt KDF: Incorrect arguments - scrypt parameters is not an object", function(t) { | ||
var kdf = new scrypt.KDF(); | ||
kdf.config.keyEncoding = "ascii"; | ||
try { | ||
kdf("key", 123); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the scrypt parameters JSON object is passed as a number"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"expecting JSON object representing scrypt parameters"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
kdf("key", 123, function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the scrypt parameters JSON object is passed as a number"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"expecting JSON object representing scrypt parameters"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Scrypt KDF: Incorrect arguments - size is not a number", function(t) { | ||
var kdf = new scrypt.KDF(); | ||
kdf.config.keyEncoding = "ascii"; | ||
try { | ||
kdf("key", scryptParameters, "this should be a number"); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the size parameter was of type string"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"size must be a number"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
kdf("key", scryptParameters, "this should be a number", function() {}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the size parameter was of type string"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"size must be a number"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Scrypt KDF: Incorrect arguments - salt is not of type string, string object nor buffer", function(t) { | ||
var kdf = new scrypt.KDF(); | ||
kdf.config.keyEncoding = "ascii"; | ||
try { | ||
kdf("key", scryptParameters, 32, 123); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the salt is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"salt must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
kdf("key", scryptParameters, 32, 123, function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the salt is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"salt must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); | ||
test("Scrypt KDF: Incorrect arguments - salt is an object, but not a string object nor a buffer", function(t) { | ||
try { | ||
var kdf = new scrypt.KDF(); | ||
kdf.config.keyEncoding = "ascii"; | ||
kdf("key", scryptParameters, 32, {}); | ||
} catch (err) { | ||
t.ok(err, "Synchronous test - An error was correctly thrown because the salt is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"salt must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
} | ||
try { | ||
kdf("key", scryptParameters, 32, {}, function(){}); | ||
} catch (err) { | ||
t.ok(err, "Asynchronous test - An error was correctly thrown because the salt is of an incorrect type"); | ||
t.deepEqual(err,scrypt.errorObject(ADDONARG,"salt must be a buffer or string"), "The correct object is returned, namely: " + JSON.stringify(err)); | ||
t.end(); | ||
} | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
540560
59
921
545
2