Compare commits

..

92 Commits

Author SHA1 Message Date
fe1ab3bf64 add progress callback
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-07 16:55:52 +02:00
5bbf9d658c reduce extreme time consumption of pbkdf
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-04 22:09:25 +02:00
78a535087a add scrypt pbkdf
All checks were successful
continuous-integration/drone/push Build is passing
2023-04-04 21:52:10 +02:00
7d41f8689f fix
All checks were successful
continuous-integration/drone/push Build is passing
2022-08-08 13:22:42 +02:00
ae34356670 update
Some checks failed
continuous-integration/drone/push Build is failing
2022-08-08 13:16:12 +02:00
cac193a341 fix
Some checks failed
continuous-integration/drone/push Build is failing
2022-08-08 13:10:24 +02:00
00f1a57f8c async
Some checks failed
continuous-integration/drone/push Build is failing
2022-08-08 13:07:06 +02:00
42857284fd update
Some checks failed
continuous-integration/drone/push Build is failing
2021-05-24 14:59:58 +02:00
ecd0195aee refactoring, asymmetric signatures and encryption
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-06 15:13:44 +01:00
612686a224 use base58 encoding for signatures
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-30 17:16:29 +01:00
142e9ec458 function timeout
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-13 13:31:48 +01:00
4d6e028fca object signature improvements
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-13 13:09:34 +01:00
b12ffc17b8 lint
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-29 12:05:51 +01:00
4004a93bb2 typo, fix tests
Some checks failed
continuous-integration/drone/push Build is failing
2020-11-29 12:01:43 +01:00
7152ae032c fix
Some checks failed
continuous-integration/drone/push Build is failing
2020-11-02 19:17:53 +01:00
bcbc59f656 fix dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-14 07:49:44 +02:00
69d509d5c7 update stryker
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-12 18:00:29 +02:00
077f656f24 publish definition
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-11 12:09:45 +02:00
712ef8d284 stryker: use per test coverage
Some checks failed
continuous-integration/drone/push Build is failing
2020-10-04 17:57:31 +02:00
2e60415625 fix stryker config
Some checks failed
continuous-integration/drone/push Build is failing
2020-10-04 12:36:06 +02:00
b2dbea979f use jasmine
All checks were successful
continuous-integration/drone/push Build is passing
2020-10-04 12:18:39 +02:00
8a8689abc5 update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
eslint: 7.7.0 ==> 7.8.1 minor
2020-09-07 13:25:23 +02:00
251974a558 update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
ava: 3.11.1 ==> 3.12.1 minor
2020-08-30 15:52:26 +02:00
d2252f203a update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
eslint: 7.6.0 ==> 7.7.0 minor
2020-08-19 08:17:06 +02:00
f02c305e43 remove unnecessary eslint disable
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-10 08:23:36 +02:00
270a64aea4 fix drone config
Some checks failed
continuous-integration/drone/push Build is failing
2020-08-07 08:11:51 +02:00
ebcf0816ea update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
ava: 3.10.1 ==> 3.11.1 minor
eslint: 7.5.0 ==> 7.6.0 minor
2020-08-04 12:47:21 +02:00
4177ce3335 update-scanner: automatic update
All checks were successful
continuous-integration/drone/push Build is passing
@sapphirecode/encoding-helper: 1.0.48 ==> 1.0.49 minor
@sapphirecode/eslint-config: 2.1.15 ==> 2.1.16 minor
2020-07-19 14:59:53 +02:00
9931302872 fix
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-19 14:19:54 +02:00
be240d8754 update-scanner: automatic update
Some checks failed
continuous-integration/drone/push Build is failing
eslint: 7.4.0 ==> 7.5.0 minor
2020-07-19 12:04:02 +02:00
b6ec3b29db update-scanner: automatic update
All checks were successful
continuous-integration/drone/push Build is passing
@sapphirecode/encoding-helper: 1.0.46 ==> 1.0.48 minor
@sapphirecode/eslint-config: 2.1.13 ==> 2.1.15 minor
@stryker-mutator/core: 3.3.0 ==> 3.3.1 minor
@stryker-mutator/javascript-mutator: 3.3.0 ==> 3.3.1 minor
ava: 3.9.0 ==> 3.10.1 minor
eslint: 7.3.1 ==> 7.4.0 minor
2020-07-10 12:24:44 +02:00
1bac9cbca1 switch to drone
All checks were successful
continuous-integration/drone/push Build is passing
2020-07-10 08:23:35 +02:00
7b81e7118b update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.45 ==> 1.0.46 minor
@sapphirecode/eslint-config: 2.1.12 ==> 2.1.13 minor
2020-07-01 09:23:51 +02:00
bb4e6e645b update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.44 ==> 1.0.45 minor
@sapphirecode/eslint-config: 2.1.10 ==> 2.1.12 minor
eslint: 7.3.0 ==> 7.3.1 minor
2020-06-24 12:43:03 +02:00
37f41a04cf update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.43 ==> 1.0.44 minor
@sapphirecode/eslint-config: 2.1.9 ==> 2.1.10 minor
eslint: 7.2.0 ==> 7.3.0 minor
2020-06-22 08:13:25 +02:00
2a4dfc879b update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.42 ==> 1.0.43 minor
@stryker-mutator/core: 3.2.4 ==> 3.3.0 minor
@stryker-mutator/javascript-mutator: 3.2.4 ==> 3.3.0 minor
ava: 3.8.2 ==> 3.9.0 minor
2020-06-19 13:00:43 +02:00
b47d0507dd update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.41 ==> 1.0.42 minor
@sapphirecode/eslint-config: 2.1.8 ==> 2.1.9 minor
eslint: 7.1.0 ==> 7.2.0 minor
2020-06-11 20:20:23 +02:00
467ff1e254 update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.40 ==> 1.0.41 minor
@sapphirecode/eslint-config: 2.1.7 ==> 2.1.8 minor
nyc: 15.0.1 ==> 15.1.0 minor
2020-06-02 08:37:50 +02:00
0294d151bb update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.39 ==> 1.0.40 minor
@sapphirecode/eslint-config: 2.1.6 ==> 2.1.7 minor
@stryker-mutator/core: 3.2.3 ==> 3.2.4 minor
@stryker-mutator/javascript-mutator: 3.2.3 ==> 3.2.4 minor
eslint: 7.0.0 ==> 7.1.0 minor
2020-05-23 18:10:05 +02:00
e2d6ebfe4a update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.38 ==> 1.0.39 minor
@sapphirecode/eslint-config: 2.1.4 ==> 2.1.6 minor
2020-05-17 19:28:50 +02:00
6d86eece90 fix 2020-05-17 18:56:46 +02:00
771e3c0907 fix 2020-05-15 16:30:28 +02:00
7a82387bc3 update 2020-05-15 16:28:17 +02:00
f157ee2d33 update jenkins.js 2020-05-15 13:17:19 +02:00
e7bff5b41c adapt jenkins.js 2020-05-13 16:07:46 +02:00
3ff9d3f959 update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.36 ==> 1.0.37 minor
ava: 3.8.1 ==> 3.8.2 minor
eslint: 6.8.0 ==> 7.0.0 major
2020-05-09 21:44:16 +02:00
88d30e9663 update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.35 ==> 1.0.36 minor
@sapphirecode/eslint-config: 2.0.24 ==> 2.0.25 minor
2020-05-08 13:31:52 +02:00
ab3bf29d61 update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.34 ==> 1.0.35 minor
@sapphirecode/eslint-config: 2.0.23 ==> 2.0.24 minor
2020-05-07 10:20:54 +02:00
c9f07a475e fix 2020-05-06 10:08:28 +02:00
19d24333c4 update-scanner: automatic update
@sapphirecode/encoding-helper: 1.0.32 ==> 1.0.34 minor
@sapphirecode/eslint-config: 2.0.19 ==> 2.0.23 minor
2020-05-06 09:52:35 +02:00
8305ab78f6 fix 2020-05-06 07:50:45 +02:00
1fc39df14f fix 2020-05-06 07:44:21 +02:00
48f87e2ef4 fix 2020-05-06 07:32:54 +02:00
fc54ff3932 fix publish 2020-05-05 19:49:50 +02:00
6577c8e10f move to @sapphirecode scope 2020-05-05 19:22:13 +02:00
a45944bf1f update-scanner: automatic update
@scode/encoding-helper: 1.0.29 ==> 1.0.31 minor
@scode/eslint-config: 2.0.15 ==> 2.0.16 minor
2020-05-04 21:02:56 +02:00
0d04b915c4 add compile script 2020-05-04 20:38:51 +02:00
bf9779dffa update-scanner: automatic update
@scode/encoding-helper: 1.0.27 ==> 1.0.29 minor
@scode/eslint-config: 2.0.13 ==> 2.0.15 minor
2020-05-03 18:16:28 +02:00
0d559539f1 only publish necessary files 2020-05-03 17:00:34 +02:00
159c1c1ced update-scanner: automatic update
@scode/encoding-helper: 1.0.26 ==> 1.0.27 minor
@scode/eslint-config: 2.0.12 ==> 2.0.13 minor
2020-05-02 16:59:47 +02:00
b3e3e4ab7e update-scanner: automatic update
@scode/encoding-helper: 1.0.25 ==> 1.0.26 minor
@scode/eslint-config: 2.0.11 ==> 2.0.12 minor
2020-04-28 07:48:15 +02:00
6cfacfb09f update-scanner: automatic update
@scode/encoding-helper: 1.0.24 ==> 1.0.25 minor
ava: 3.8.0 ==> 3.8.1 minor
2020-04-27 14:00:53 +02:00
6e4ac8bdee update-scanner: automatic update
@scode/encoding-helper: 1.0.20 ==> 1.0.24 minor
ava: 3.7.1 ==> 3.8.0 minor
2020-04-27 13:08:59 +02:00
d9dd021007 update-scanner: automatic update
@scode/encoding-helper: 1.0.19 ==> 1.0.20 minor
@scode/eslint-config: 2.0.10 ==> 2.0.11 minor
ava: 3.7.0 ==> 3.7.1 minor
2020-04-21 11:23:39 +02:00
4082e582af update-scanner: automatic update
@scode/encoding-helper: 1.0.17 ==> 1.0.19 minor
@scode/eslint-config: 2.0.7 ==> 2.0.10 minor
ava: 3.6.0 ==> 3.7.0 minor
2020-04-14 15:45:55 +02:00
7fc5cebb67 fix lint script 2020-04-14 14:57:47 +02:00
7e68e45ec4 update-scanner: automatic update
@scode/encoding-helper: 1.0.16 ==> 1.0.17 minor
@scode/eslint-config: 2.0.6 ==> 2.0.7 minor
2020-04-09 09:44:58 +02:00
1f0b0a377a update-scanner: automatic update 2020-04-08 13:13:05 +02:00
7211bcc925 update-scanner: automatic update 2020-04-06 10:07:27 +02:00
d3a7bbbdb2 fix 2020-04-06 09:18:30 +02:00
baaa8e9b17 update-scanner: automatic update 2020-04-06 08:55:31 +02:00
a80f8504ff update-scanner: automatic update 2020-04-06 07:57:45 +02:00
b8741ea83a add mutate script 2020-03-30 11:57:22 +02:00
64dc7086fd update-scanner: automatic update 2020-03-30 08:38:06 +02:00
3f0b9abc21 update-scanner: automatic update 2020-03-26 14:43:25 +01:00
d72790e636 fix license 2020-03-25 17:02:59 +01:00
1b303ecea5 update-scanner: automatic update 2020-03-24 09:35:23 +01:00
d8dc182521 update-scanner: automatic update 2020-03-19 07:12:56 +01:00
f6ec536481 update-scanner: automatic update 2020-03-14 14:31:33 +01:00
729a65839c update-scanner: automatic update 2020-03-12 09:45:25 +01:00
18476b2aaf update-scanner: automatic update 2020-03-12 09:38:27 +01:00
565dab36ac update 2020-03-12 09:17:13 +01:00
2e2ea43310 add encryption mode cbc_256_quick 2020-03-11 09:21:33 +01:00
38542bb422 return null on error with signed objects 2020-03-10 13:37:12 +01:00
3715ec5183 formatting 2020-03-09 10:12:40 +01:00
8f227d8c0e add tests for signature without timeout 2020-03-09 08:36:20 +01:00
cb9c142547 optimize random_string 2020-03-09 08:27:14 +01:00
c0ec49fd4b add length test for random string generators 2020-03-09 08:22:39 +01:00
e470b68ea5 fix test for zero length random string 2020-03-09 08:14:55 +01:00
59476a8087 remove explicit default encoding 2020-03-09 08:07:44 +01:00
c603fccd6f add test for aes 128 encryption 2020-03-09 08:04:16 +01:00
91f7d66dbb test for invalid length on random hex 2020-03-09 07:58:26 +01:00
30 changed files with 3865 additions and 3887 deletions

14
.drone.yml Normal file
View File

@ -0,0 +1,14 @@
kind: pipeline
name: default
steps:
- name: setup
image: registry:5000/node-build
commands:
- yarn
- curl https://git.scode.ovh/Timo/standard/raw/branch/master/ci.js > ci.js
- name: build
image: registry:5000/node-build
commands:
- node ci.js

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
*.d.ts

View File

@ -1,17 +1,22 @@
/*
* Copyright (C) Sapphirecode - All Rights Reserved
* This file is part of crypto-helper which is released under MIT.
* See file 'LICENSE' for full license details.
* Created by Timo Hocker <timo@scode.ovh>, May 2020
*/
'use strict';
module.exports = {
env: {
commonjs: true,
es6: true,
node: true
es6: true,
node: true
},
extends: [
'@scode'
],
extends: [ '@sapphirecode' ],
globals: {
Atomics: 'readonly',
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018
}
}
parserOptions: { ecmaVersion: 2018 }
};

2
.gitignore vendored
View File

@ -5,3 +5,5 @@
# stryker temp files
.stryker-tmp
*.log
*.d.ts

8
.liconfig.json Normal file
View File

@ -0,0 +1,8 @@
{
"has_license": true,
"license": "MIT",
"author": "Timo Hocker",
"company": "Sapphirecode",
"email": "timo@scode.ovh",
"software": "crypto-helper"
}

1
.npmrc
View File

@ -1 +0,0 @@
@scode:registry=https://npm.scode.ovh

31
CHANGELOG.md Normal file
View File

@ -0,0 +1,31 @@
# CHANGELOG
## 2.1.0
Added scrypt key derivation function
## 2.0.0
Signature related functions have changed to be asynchronous and to accept async
functions as key retrieving functions
## 1.3.0
asymmetric encryption and signatures
object signing: ability to use rsa keys
## 1.2.0
object signing:
- custom properties
- keys retrievable using functions
## 1.1.0
aes encryption functions
## 1.0.0
initial release

23
Jenkinsfile vendored
View File

@ -1,23 +0,0 @@
pipeline {
agent any
environment {
VERSION = VersionNumber([
versionNumberString:
'${BUILDS_ALL_TIME}',
versionPrefix: '1.1.',
worstResultForIncrement: 'SUCCESS'
])
}
stages {
stage('Building') {
steps {
script {
currentBuild.displayName = env.VERSION
}
sh 'yarn ci ${VERSION}'
}
}
}
}

7
LICENSE Normal file
View File

@ -0,0 +1,7 @@
MIT License Copyright (c) <year> <author>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,20 +1,67 @@
# Crypto Helper
# @sapphirecode/crypto-helper
Helper functions for cryptography
version: 2.1.x
simple functions for cryptography
## Installation
npm:
> npm i --save @sapphirecode/crypto-helper
yarn:
> yarn add @sapphirecode/crypto-helper
## Usage
### Examples
```js
const crypto = require('@scode/crypto-helper');
const crypto = require('@sapphirecode/crypto-helper');
const rand_hex = crypto.random_hex(16); // outputs 16 byte random hex
const rand_salt = crypto.create_salt(); // same as random_hex, but with fixed length of 32 bytes
const random_string = crypto.random_string(16) // output 16 character long random string
const random_string = crypto.random_string(16); // output 16 character long random string
const hash = crypto.hash_sha512(random_string, random_hex); // returns sha 512 hex
const check = crypto.checksum('foo'); // returns a sha 256 hex
const scrypt_hash = await crypto.pbkdf_scrypt('foo', 'bar'); // returns a scrypt hash
// jwt like object signing
const signed = crypto.sign_object({foo: 'bar'}, 'secret');
const dec = crypto.decode_signed(signed); // decode a signed object without verifying the signature
const ver = crypto.verify_signature(signed, 'secret', 10000); // verifies the signature and returns the contents. the timeout is in milliseconds and optional, timing will be ignored if omitted.
const info = await crypto.get_signature_info(signed); // returns an object with iat (issued at), key_info and data
const dec = await crypto.decode_signed(signed); // decode a signed object without verifying the signature
const ver = await crypto.verify_signature(signed, 'secret', 10000); // verifies the signature and returns the contents. the timeout is in milliseconds and optional, timing will be ignored if omitted.
const ver_info = await crypto.verify_signature_get_info(signed, 'secret', 10000); // verify a signature and get signature information like iat and key_info
const ver_func = await crypto.verify_signature(
signed,
(signature_info) => 'secret',
10000
); // verify a signature, retrieve the key using the signature info
// encryption
const enc = crypto.encrypt_aes('foo', 'bar');
const dec = crypto.decrypt_aes(enc, 'bar');
// asymmetric encryption and signatures
const keys = await crypto.generate_keypair(2048); // generate private and public key (length is optional and 2048 by default)
const aenc = crypto.asym_encrypt('foo', keys.public_key); // encrypt
const adec = crypto.asym_decrypt(aenc, key.private_key); // decrypt
const asig = crypto.asym_sign('foo', keys.private_key); // create signature
const aver = crypto.asym_verify('foo', keys.public_key, asig); // verify signature, returns boolean
```
### Asymmetric signatures on object signing
the functions `sign_object`, `verify_signature`, ... will automatically detect
rsa keys and use them to sign objects asymmetrically. Note that keys have to be
provided in the correct order (private key for signing, public key for
verifying). Else the keys will just be interpreted as symmetric and verification
will fail.
## License
MIT © Timo Hocker <timo@scode.ovh>

248
index.js
View File

@ -1,243 +1,25 @@
/*
* Copyright (C) Sapphirecode - All Rights Reserved
* Created by Timo Hocker <timo@scode.ovh>, March 2020
* This file is part of crypto-helper which is released under MIT.
* See file 'LICENSE' for full license details.
* Created by Timo Hocker <timo@scode.ovh>, May 2020
*/
// @ts-nocheck
/* eslint-disable no-magic-numbers */
'use strict';
const crypto = require ('crypto');
const encoding = require ('@scode/encoding-helper');
const encryption_mode_cbc_256 = {
algorithm: 'aes-256-cbc',
nonce_size: 16,
key_size: 32,
hash: 'sha256',
salt_size: 16,
iterations: 32767
};
const encryption_mode_cbc_128 = {
algorithm: 'aes-128-cbc',
nonce_size: 16,
key_size: 16,
hash: 'sha256',
salt_size: 16,
iterations: 40
};
/**
* creates a random string
*
* @param {number} len string length default: 6
* @returns {string} random string
*/
function random_string (len = 8) {
if (len < 1)
throw new Error ('invalid length');
return crypto.randomBytes (Math.ceil (len * 3 / 4))
.toString ('base64')
.replace (/[=]+$/u, '')
.substr (0, len);
}
/**
* creates a random hexadecimal string
*
* @param {number} len length
* @returns {string} hex string
*/
function random_hex (len = 8) {
if (len < 1)
throw new Error ('invalid length');
return crypto.randomBytes (Math.ceil (len / 2))
.toString ('hex')
.substr (0, len);
}
/**
* creates a 64 character long random hex string
*
* @returns {string} salt
*/
function create_salt () {
return random_hex (64);
}
/**
* creates a sha512 hash
*
* @param {string} str string input
* @param {string} salt salt
* @returns {string} salt
*/
function hash_sha512 (str, salt) {
const md = crypto.createHash ('sha512');
md.update (str);
md.update (salt);
return md.digest ('hex');
}
/**
* sign an object
*
* @param {any} obj object to sign
* @param {string} key key to use
* @param {string} key_info key identifier
* @returns {string} signed object
*/
function sign_object (obj, key, key_info = null) {
const payload = { iat: Date.now (), key_info, obj };
const str = encoding.to_b64 (JSON.stringify (payload));
const token = encoding.to_b64 (hash_sha512 (str, key), 'hex');
const res = `${str}.${token}`;
return encodeURIComponent (res);
}
/**
* verify a signed object and return its contents
*
* @param {string} str string to verify
* @param {string} key used key
* @param {number} timeout timeout (optional)
* @returns {any} returns object if successful, else null
*/
function verify_signature (str, key, timeout = 0) {
const dec = decodeURIComponent (str)
.split ('.');
const json = JSON.parse (encoding.to_utf8 (dec[0], 'base64'));
const token = encoding.to_hex (dec[1], 'base64');
const verify_token = hash_sha512 (dec[0], key);
if (token !== verify_token)
return null;
const time = Date.now () - json.iat;
if (timeout !== 0 && time > timeout)
return null;
return json.obj;
}
/**
* get a signed object info and data
*
* @param {string} str string to decode
* @returns {any} data
*/
function get_signature_info (str) {
const dec = decodeURIComponent (str)
.split ('.');
const json = JSON.parse (encoding.to_utf8 (dec[0], 'base64'));
return json;
}
/**
* decode a signed object without verifying the signature
*
* @param {string} str string to decode
* @returns {any} object
*/
function decode_signed (str) {
return get_signature_info (str).obj;
}
/**
* creates a sha256 hash
*
* @param {any} data input
* @returns {string} hash
*/
function checksum (data) {
const md = crypto.createHash ('sha256');
md.update (String (data));
return md.digest ('hex');
}
/**
* encrypt plain text with aes
*
* @param {string} text plaintext
* @param {string} pass password
* @param {object} mode encryption mode
* @returns {string} encrypted
*/
function encrypt_aes (text, pass, mode = encryption_mode_cbc_256) {
const salt = crypto.randomBytes (mode.salt_size);
// eslint-disable-next-line no-sync
const key = crypto.pbkdf2Sync (
Buffer.from (pass, 'utf-8'),
salt,
mode.iterations,
mode.key_size,
mode.hash
);
const nonce = crypto.randomBytes (mode.nonce_size);
const cipher = crypto.createCipheriv (mode.algorithm, key, nonce);
return Buffer.concat ([
salt,
nonce,
cipher.update (Buffer.from (text)),
cipher.final ()
])
.toString ('base64');
}
/**
* decrypt an aes string
*
* @param {string} ciphertext encrypted text
* @param {string} pass password
* @param {object} mode encryption mode
* @param {boolean} rethrow rethrow exceptions instead of returning null
* @returns {string} plaintext
*/
function decrypt_aes (
ciphertext,
pass,
mode = encryption_mode_cbc_256,
rethrow = false
) {
try {
let buf = Buffer.from (ciphertext, 'base64');
const salt = buf.slice (0, mode.salt_size);
buf = buf.slice (mode.salt_size);
// eslint-disable-next-line no-sync
const key = crypto.pbkdf2Sync (
Buffer.from (pass, 'utf-8'),
salt,
mode.iterations,
mode.key_size,
mode.hash
);
const nonce = buf.slice (0, mode.nonce_size);
buf = buf.slice (mode.nonce_size);
const cipher = crypto.createDecipheriv (mode.algorithm, key, nonce);
return Buffer.concat ([
cipher.update (buf),
cipher.final ()
])
.toString ('utf-8');
}
catch (e) {
if (rethrow)
throw e;
}
return null;
}
const encryption = require ('./lib/encryption');
const hashing = require ('./lib/hashing');
const random = require ('./lib/random');
const signatures = require ('./lib/signatures');
const rsa = require ('./lib/rsa');
const pbkdf = require ('./lib/pbkdf');
module.exports = {
checksum,
create_salt,
decode_signed,
decrypt_aes,
encrypt_aes,
encryption_mode_cbc_128,
encryption_mode_cbc_256,
get_signature_info,
hash_sha512,
random_hex,
random_string,
sign_object,
verify_signature
...random,
...hashing,
...encryption,
...signatures,
...rsa,
...pbkdf
};

14
jasmine.json Normal file
View File

@ -0,0 +1,14 @@
{
"spec_dir": "test",
"spec_files": [
"spec/*.js",
"spec/*.ts"
],
"helpers": [
"helpers/*.js",
"helpers/*.ts"
],
"stopSpecOnExpectationFailure": false,
"random": false
}

View File

@ -1,26 +0,0 @@
/* eslint-disable no-process-exit */
/* eslint-disable no-console */
/* eslint-disable no-sync */
'use strict';
const fs = require ('fs');
const child_process = require ('child_process');
const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8'));
[
,, pkg.version
] = process.argv;
fs.writeFileSync ('package.json', JSON.stringify (pkg, null, 2));
child_process.execSync ('yarn lint', { stdio: 'inherit' });
if (typeof pkg.scripts !== 'undefined' && typeof pkg.scripts.test === 'string')
child_process.execSync ('yarn test', { stdio: 'inherit' });
child_process.exec ('git log -1 | grep \'\\[no publish\\]\'')
.addListener ('exit', (code) => {
if (code === 0) {
console.log ('build not marked for deployment');
process.exit (1);
}
else { child_process.execSync ('yarn publish'); }
});

110
lib/encryption.js Normal file
View File

@ -0,0 +1,110 @@
'use strict';
const crypto = require ('crypto');
const encryption_mode_cbc_256 = {
algorithm: 'aes-256-cbc',
nonce_size: 16,
key_size: 32,
hash: 'sha256',
salt_size: 16,
iterations: 32767
};
const encryption_mode_cbc_256_quick = {
algorithm: 'aes-256-cbc',
nonce_size: 16,
key_size: 32,
hash: 'sha256',
salt_size: 16,
iterations: 32
};
const encryption_mode_cbc_128 = {
algorithm: 'aes-128-cbc',
nonce_size: 16,
key_size: 16,
hash: 'sha256',
salt_size: 16,
iterations: 40
};
/**
* encrypt plain text with aes
*
* @param {string} text plaintext
* @param {string} pass password
* @param {object} mode encryption mode
* @returns {string} encrypted
*/
function encrypt_aes (text, pass, mode = encryption_mode_cbc_256) {
const salt = crypto.randomBytes (mode.salt_size);
// eslint-disable-next-line no-sync
const key = crypto.pbkdf2Sync (
Buffer.from (pass),
salt,
mode.iterations,
mode.key_size,
mode.hash
);
const nonce = crypto.randomBytes (mode.nonce_size);
const cipher = crypto.createCipheriv (mode.algorithm, key, nonce);
return Buffer.concat ([
salt,
nonce,
cipher.update (Buffer.from (text)),
cipher.final ()
])
.toString ('base64');
}
/**
* decrypt an aes string
*
* @param {string} ciphertext encrypted text
* @param {string} pass password
* @param {object} mode encryption mode
* @param {boolean} rethrow rethrow exceptions instead of returning null
* @returns {string} plaintext
*/
function decrypt_aes (
ciphertext,
pass,
mode = encryption_mode_cbc_256,
rethrow = false
) {
try {
let buf = Buffer.from (ciphertext, 'base64');
const salt = buf.slice (0, mode.salt_size);
buf = buf.slice (mode.salt_size);
// eslint-disable-next-line no-sync
const key = crypto.pbkdf2Sync (
Buffer.from (pass),
salt,
mode.iterations,
mode.key_size,
mode.hash
);
const nonce = buf.slice (0, mode.nonce_size);
buf = buf.slice (mode.nonce_size);
const cipher = crypto.createDecipheriv (mode.algorithm, key, nonce);
return Buffer.concat ([
cipher.update (buf),
cipher.final ()
])
.toString ();
}
catch (e) {
if (rethrow)
throw e;
}
return null;
}
module.exports = {
decrypt_aes,
encrypt_aes,
encryption_mode_cbc_128,
encryption_mode_cbc_256,
encryption_mode_cbc_256_quick
};

34
lib/hashing.js Normal file
View File

@ -0,0 +1,34 @@
'use strict';
const crypto = require ('crypto');
/**
* creates a sha256 hash
*
* @param {any} data input
* @returns {string} hash
*/
function checksum (data) {
const md = crypto.createHash ('sha256');
md.update (String (data));
return md.digest ('hex');
}
/**
* creates a sha512 hash
*
* @param {string} str string input
* @param {string} salt salt
* @returns {string} salt
*/
function hash_sha512 (str, salt) {
const md = crypto.createHash ('sha512');
md.update (str);
md.update (salt);
return md.digest ('hex');
}
module.exports = {
hash_sha512,
checksum
};

21
lib/pbkdf.js Normal file
View File

@ -0,0 +1,21 @@
'use strict';
const { scrypt } = require ('scrypt-js');
/**
* creates a scrypt hash
*
* @param {string} str string input
* @param {string} salt salt#
* @param {(progress: number) => void} [progress] progress callback
* @returns {Promise<string>} hash
*/
async function pbkdf_scrypt (str, salt, progress) {
const bstr = Buffer.from (str.normalize ('NFKC'), 'utf-8');
const bsalt = Buffer.from (salt.normalize ('NFKC'), 'utf-8');
const hash = await scrypt (bstr, bsalt, 8192, 8, 1, 64, progress);
return Buffer.from (hash)
.toString ('hex');
}
module.exports = { pbkdf_scrypt };

46
lib/random.js Normal file
View File

@ -0,0 +1,46 @@
'use strict';
const crypto = require ('crypto');
/**
* creates a random string
*
* @param {number} len string length default: 8
* @returns {string} random string
*/
function random_string (len = 8) {
if (len < 1)
throw new Error ('invalid length');
return crypto.randomBytes (Math.ceil (len * 3 / 4))
.toString ('base64')
.substr (0, len);
}
/**
* creates a random hexadecimal string
*
* @param {number} len length
* @returns {string} hex string
*/
function random_hex (len = 8) {
if (len < 1)
throw new Error ('invalid length');
return crypto.randomBytes (Math.ceil (len / 2))
.toString ('hex')
.substr (0, len);
}
/**
* creates a 64 character long random hex string
*
* @returns {string} salt
*/
function create_salt () {
return random_hex (64);
}
module.exports = {
create_salt,
random_hex,
random_string
};

98
lib/rsa.js Normal file
View File

@ -0,0 +1,98 @@
'use strict';
const crypto = require ('crypto');
/**
* generate a new rsa keypair
*
* @param {number} length the key length in bit. default: 2048
* @returns {Promise<{public_key: string, private_key: string}>} generated keys
*/
async function generate_keypair (length = 2048) {
const key = await new Promise (
(res, rej) => crypto.generateKeyPair (
'rsa',
{
modulusLength: length,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
},
(err, public_key, private_key) => {
if (err)
rej (err);
res ({ public_key, private_key });
}
)
);
return key;
}
/**
* encrypts data using a public key
* it can only be decrypted with the corresponding private key
*
* @param {string} data data to encrypt
* @param {string} public_key public key
* @returns {string} encrypted data
*/
function asym_encrypt (data, public_key) {
return crypto.publicEncrypt (public_key, Buffer.from (data))
.toString ('base64');
}
/**
* decrypts data using a private key
*
* @param {string} data data to decrypt
* @param {string} private_key private key
* @returns {string} decrypted data
*/
function asym_decrypt (data, private_key) {
return crypto.privateDecrypt (private_key, Buffer.from (data, 'base64'))
.toString ();
}
/**
* creates a signature using a private key
* can later be verified using the corresponding public key
*
* @param {string} data data to sign
* @param {string} private_key private key
* @returns {string} signature
*/
function asym_sign (data, private_key) {
const sign = crypto.createSign ('sha256');
sign.write (data);
sign.end ();
return sign.sign (private_key, 'hex');
}
/**
* verifies a signature using a public key
*
* @param {string} data data to verify
* @param {string} public_key public key
* @param {string} signature signature to verify
* @returns {boolean} true if signature is valid
*/
function asym_verify (data, public_key, signature) {
const verify = crypto.createVerify ('sha256');
verify.write (data);
verify.end ();
return verify.verify (public_key, signature, 'hex');
}
module.exports = {
generate_keypair,
asym_encrypt,
asym_decrypt,
asym_sign,
asym_verify
};

135
lib/signatures.js Normal file
View File

@ -0,0 +1,135 @@
'use strict';
const encoding = require ('@sapphirecode/encoding-helper');
const { hash_sha512 } = require ('./hashing');
const { asym_sign, asym_verify } = require ('./rsa');
/**
* sign an object
*
* @param {any} obj object to sign
* @param {string} key key to use
* @param {string|Object} key_info key identifier
* @returns {string} signed object
*/
function sign_object (obj, key, key_info = null) {
const payload = {
iat: Date.now (),
obj,
...(typeof key_info === 'object' ? key_info : { key_info })
};
const str = encoding.to_b58 (JSON.stringify (payload));
const is_rsa = (/^-----BEGIN PRIVATE KEY-----/u).test (key);
const signature = is_rsa ? asym_sign (str, key) : hash_sha512 (str, key);
const token = encoding.to_b58 (signature, 'hex');
const res = `${str}.${token}.2`;
return res;
}
/**
* parse a string signature
*
* @param {string} str string to verify
* @param {string|((Object)=>string|Promise<string>)|null} key used key
* @returns {Promise<any>} returns object if successful, else null
*/
async function parse_signature (str, key = null) {
let dec = str.split ('.');
const version = dec[2];
const res = {};
switch (version) {
case '2':
res.json = JSON.parse (encoding.to_utf8 (dec[0], 'base58'));
res.token = encoding.to_hex (dec[1], 'base58');
break;
default:
dec = decodeURIComponent (str)
.split ('.');
res.json = JSON.parse (
encoding.to_utf8 (dec[0], 'base64')
);
res.token = encoding.to_hex (dec[1], 'base64');
break;
}
if (key !== null) {
const string_key = typeof key === 'string' ? key : await key (res.json);
res.is_rsa = (/^-----BEGIN RSA PUBLIC KEY-----/u).test (string_key);
res.hash = res.is_rsa
? asym_verify (dec[0], string_key, res.token)
: hash_sha512 (dec[0], string_key);
}
return res;
}
/**
* verify a signed object and return its info and contents
*
* @param {string} str string to verify
* @param {string|((Object)=>string|Promise<string>)} key used key
* @param {number|((Object)=>number|Promise<number>)} timeout timeout (optional)
* @returns {Promise<any>} returns object if successful, else null
*/
async function verify_signature_get_info (str, key, timeout = 0) {
if (typeof str !== 'string')
return null;
const { json, token, hash, is_rsa } = await parse_signature (str, key);
if (is_rsa ? !hash : (token !== hash))
return null;
const time = Date.now () - json.iat;
const num_timeout = typeof timeout === 'number'
? timeout
: await timeout (json);
if (num_timeout === 0 || time <= num_timeout)
return json;
return null;
}
/**
* verify a signed object and return its contents
*
* @param {string} str string to verify
* @param {string|((Object)=>string|Promise<string>)} key used key
* @param {number|((Object)=>number|Promise<number>)} timeout timeout (optional)
* @returns {Promise<any>} returns object if successful, else null
*/
async function verify_signature (str, key, timeout = 0) {
const res = await verify_signature_get_info (str, key, timeout);
if (res === null)
return null;
return res.obj;
}
/**
* get a signed object info and data
*
* @param {string} str string to decode
* @returns {Promise<any>} data
*/
async function get_signature_info (str) {
if (typeof str !== 'string')
return null;
const { json } = await parse_signature (str);
return json;
}
/**
* decode a signed object without verifying the signature
*
* @param {string} str string to decode
* @returns {Promise<any>} object
*/
async function decode_signed (str) {
const info = await get_signature_info (str);
if (info)
return info.obj;
return null;
}
module.exports = {
decode_signed,
get_signature_info,
sign_object,
verify_signature,
verify_signature_get_info
};

View File

@ -1,23 +1,50 @@
{
"name": "@scode/crypto-helper",
"version": "1.0.0",
"name": "@sapphirecode/crypto-helper",
"version": "2.1.2",
"main": "index.js",
"author": "Timo Hocker <t-hocker@web.de>",
"author": {
"name": "Timo Hocker",
"email": "timo@scode.ovh"
},
"bugs": "https://redmine.scode.ovh/projects/crypto-helper",
"license": "MIT",
"keywords": [
"cryptography",
"crypto",
"hashing"
],
"repository": {
"type": "git",
"url": "https://git.scode.ovh:timo/crypto-helper.git"
},
"description": "simple functions for cryptography",
"devDependencies": {
"@scode/eslint-config": "^1.2.25",
"@stryker-mutator/core": "^2.5.0",
"@stryker-mutator/javascript-mutator": "^2.5.0",
"ava": "^3.5.0",
"eslint": "^6.8.0",
"nyc": "^15.0.0"
"@sapphirecode/eslint-config": "^2.1.4",
"@stryker-mutator/core": "^6.1.2",
"@stryker-mutator/jasmine-runner": "^6.1.2",
"@types/jasmine": "^4.0.3",
"eslint": "^8.21.0",
"jasmine": "^4.3.0",
"nyc": "^15.0.1"
},
"scripts": {
"lint": "eslint .",
"test": "nyc ava",
"ci": "yarn && node jenkins.js"
"lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs",
"test": "nyc jasmine --config=\"jasmine.json\"",
"mutate": "stryker run",
"compile": "tsc --allowJs --declaration --emitDeclarationOnly --lib es6 index.js"
},
"dependencies": {
"@scode/encoding-helper": "^1.0.1"
"@sapphirecode/encoding-helper": "^1.1.0",
"scrypt-js": "^3.0.1"
},
"files": [
"LICENSE",
"lib/*.js",
"lib/*.d.ts",
"index.js",
"index.d.ts"
],
"engines": {
"node": ">=10.12.0"
}
}

View File

@ -1,11 +1,29 @@
module.exports = function(config) {
config.set({
mutator: "javascript",
packageManager: "yarn",
reporters: ["clear-text"],
testRunner: "command",
transpilers: [],
coverageAnalysis: "all",
mutate: ["index.js"]
});
/*
* Copyright (C) Sapphirecode - All Rights Reserved
* This file is part of crypto-helper which is released under MIT.
* See file 'LICENSE' for full license details.
* Created by Timo Hocker <timo@scode.ovh>, May 2020
*/
'use strict';
/**
* @type {import('@stryker-mutator/api/core').StrykerOptions}
*/
module.exports = {
packageManager: 'yarn',
reporters: [
'clear-text',
'progress'
],
testRunner: 'jasmine',
jasmineConfigFile: 'jasmine.json',
coverageAnalysis: 'perTest',
mutate: [
'**/*.js',
'**/*.ts',
'!**/test/**/*',
'!**/spec/**/*',
'!stryker.conf.js'
]
};

View File

@ -1,50 +0,0 @@
/* eslint-disable no-magic-numbers */
// @ts-nocheck
'use strict';
const test = require ('ava');
const crypto = require ('../index');
test ('encryption', (t) => {
const enc = crypto.encrypt_aes ('foo', 'bar');
t.is (typeof enc, 'string');
});
test ('decryption', (t) => {
const enc = crypto.encrypt_aes ('foo', 'bar');
const dec = crypto.decrypt_aes (enc, 'bar');
t.is (dec, 'foo');
});
test ('fail decryption', (t) => {
const enc = crypto.encrypt_aes ('foo', 'bar');
const dec = crypto.decrypt_aes (enc, 'baz');
t.is (dec, null);
});
test ('rethrow decryption', (t) => {
const enc = crypto.encrypt_aes ('foo', 'bar');
t.throws (() => {
crypto.decrypt_aes (
enc,
'baz',
crypto.encryption_mode_cbc_256,
true
);
});
});
test ('unique crypto strings', (t) => {
const enc = [
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar')
];
const unique = enc.filter ((v, i) => enc.indexOf (v) === i).length;
t.is (unique, 8);
});

View File

@ -1,121 +0,0 @@
/*
* Copyright (C) Sapphirecode - All Rights Reserved
* Created by Timo Hocker <timo@scode.ovh>, March 2020
*/
/* eslint-disable no-magic-numbers */
// @ts-nocheck
'use strict';
const test = require ('ava');
const crypto = require ('../index');
test ('random_hex', (t) => {
const hex = crypto.random_hex (16);
t.is (hex.length, 16);
t.regex (hex, /^[0-9a-f]+$/iu);
});
test ('random_hex with default length', (t) => {
const hex = crypto.random_hex ();
t.is (hex.length, 8);
t.regex (hex, /^[0-9a-f]+$/iu);
});
test ('random_hex should refuse lenght smaller 1', (t) => {
t.throws (() => (crypto.random_hex (0)));
});
test ('random_string', (t) => {
const str = crypto.random_string (16);
t.is (str.length, 16);
});
test ('random_string with default length', (t) => {
const str = crypto.random_string ();
t.is (str.length, 8);
});
test ('random_string should refuse lenght smaller 1', (t) => {
t.throws (() => (crypto.random_string (0)));
});
test ('hash_sha512', (t) => {
const hash = crypto.hash_sha512 ('a', 'b');
t.is (
hash,
// eslint-disable-next-line max-len
'2d408a0717ec188158278a796c689044361dc6fdde28d6f04973b80896e1823975cdbf12eb63f9e0591328ee235d80e9b5bf1aa6a44f4617ff3caf6400eb172d'
);
});
test ('checksum', (t) => {
const hash = crypto.checksum ('foo');
t.is (
hash,
// eslint-disable-next-line max-len
'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
);
});
test ('create_salt', (t) => {
const salt = crypto.create_salt ();
t.is (salt.length, 64);
t.regex (salt, /^[0-9a-f]+$/iu);
});
test ('sign_object', (t) => {
const obj = { foo: 'bar' };
t.notThrows (() => {
const str = crypto.sign_object (obj, 'baz');
t.is (typeof str, 'string');
});
});
test ('decode_signed', (t) => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = crypto.decode_signed (str);
t.deepEqual (obj, dec);
});
test ('verify_signature', (t) => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = crypto.verify_signature (str, 'baz');
t.deepEqual (obj, dec);
});
test ('reject tampered signatures', (t) => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = crypto.verify_signature (str, 'foo');
t.is (dec, null);
});
test ('reject old signatures', async (t) => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
await new Promise ((res) => {
setTimeout (res, 10);
});
const dec = crypto.verify_signature (str, 'baz', 1);
t.is (dec, null);
});
test ('do not reject valid signatures', async (t) => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
await new Promise ((res) => {
setTimeout (res, 10);
});
const dec = crypto.verify_signature (str, 'baz', 100);
t.deepEqual (obj, dec);
});
test ('decode problematic token', (t) => {
// eslint-disable-next-line max-len
const str = 'eyJpYXQiOjE1ODE0NDAwMTIyODgsIm9iaiI6eyJpZCI6MX19.24ZOsWrnfkNe%2FbM0r7DaVJMqE2bfn2aAM%2BZSzWeSf31OCTlXXNWD34RBL2X5v3UliYQ4IIsLNBFbaW9texPHug%3D%3D';
const obj = crypto.decode_signed (str);
t.deepEqual (obj, { id: 1 });
});

116
test/spec/encryption.js Normal file
View File

@ -0,0 +1,116 @@
/*
* Copyright (C) Sapphirecode - All Rights Reserved
* This file is part of crypto-helper which is released under MIT.
* See file 'LICENSE' for full license details.
* Created by Timo Hocker <timo@scode.ovh>, May 2020
*/
// @ts-nocheck
'use strict';
const crypto = require ('../../index');
// eslint-disable-next-line max-lines-per-function
describe ('encryption', () => {
it ('encryption', () => {
const enc = crypto.encrypt_aes ('foo', 'bar');
expect (typeof enc)
.toEqual ('string');
});
it ('decryption', () => {
const enc = crypto.encrypt_aes ('foo', 'bar');
const dec = crypto.decrypt_aes (enc, 'bar');
expect (dec)
.toEqual ('foo');
});
it ('encryption 128', () => {
const enc = crypto.encrypt_aes (
'foo',
'bar',
crypto.encryption_mode_cbc_128
);
expect (typeof enc)
.toEqual ('string');
});
it ('decryption 128', () => {
const enc = crypto.encrypt_aes (
'foo',
'bar',
crypto.encryption_mode_cbc_128
);
const dec = crypto.decrypt_aes (
enc,
'bar',
crypto.encryption_mode_cbc_128
);
expect (dec)
.toEqual ('foo');
});
it ('encryption 256_quick', () => {
const enc = crypto.encrypt_aes (
'foo',
'bar',
crypto.encryption_mode_cbc_256_quick
);
expect (typeof enc)
.toEqual ('string');
});
it ('decryption 256_quick', () => {
const enc = crypto.encrypt_aes (
'foo',
'bar',
crypto.encryption_mode_cbc_256_quick
);
const dec = crypto.decrypt_aes (
enc,
'bar',
crypto.encryption_mode_cbc_256_quick
);
expect (dec)
.toEqual ('foo');
});
it ('fail decryption', () => {
const enc = crypto.encrypt_aes ('foo', 'bar');
const dec = crypto.decrypt_aes (enc, 'baz');
expect (dec)
.toEqual (null);
});
it ('rethrow decryption', () => {
const enc = crypto.encrypt_aes ('foo', 'bar');
expect (() => {
crypto.decrypt_aes (
enc,
'baz',
crypto.encryption_mode_cbc_256,
true
);
})
.toThrowError (
// eslint-disable-next-line max-len
/bad decrypt/ui
);
});
it ('unique crypto strings', () => {
const enc = [
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar'),
crypto.encrypt_aes ('foo', 'bar')
];
const unique = enc.filter ((v, i) => enc.indexOf (v) === i).length;
expect (unique)
.toEqual (8);
});
});

27
test/spec/hashing.js Normal file
View File

@ -0,0 +1,27 @@
'use strict';
const crypto = require ('../../index');
describe ('hashing', () => {
it ('sha512', () => {
const hash = crypto.hash_sha512 ('a', 'b');
expect (
hash
)
.toEqual (
// eslint-disable-next-line max-len
'2d408a0717ec188158278a796c689044361dc6fdde28d6f04973b80896e1823975cdbf12eb63f9e0591328ee235d80e9b5bf1aa6a44f4617ff3caf6400eb172d'
);
});
it ('checksum', () => {
const hash = crypto.checksum ('foo');
expect (
hash
)
.toEqual (
// eslint-disable-next-line max-len
'2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae'
);
});
});

16
test/spec/pbkdf.js Normal file
View File

@ -0,0 +1,16 @@
'use strict';
const crypto = require ('../../index');
describe ('hashing', () => {
it ('scrypt', async () => {
const hash = await crypto.pbkdf_scrypt ('a', 'b');
expect (
hash
)
.toEqual (
// eslint-disable-next-line max-len
'f70baff6e80e83a2fc5af752f4b57b6b73442f913fc6446b6cb62e3f5f56f9b18c51c3461081239fdac6e9f8498a95ecfabb0b927db8a9ee2de80ed40469b034'
);
});
});

72
test/spec/random.js Normal file
View File

@ -0,0 +1,72 @@
'use strict';
const crypto = require ('../../index');
// eslint-disable-next-line max-lines-per-function
describe ('random', () => {
it ('random_hex', () => {
const hex = crypto.random_hex (16);
expect (hex.length)
.toEqual (16);
expect (hex)
.toMatch (/^[0-9a-f]+$/iu);
});
it ('random_hex with default length', () => {
const hex = crypto.random_hex ();
expect (hex.length)
.toEqual (8);
expect (hex)
.toMatch (/^[0-9a-f]+$/iu);
});
it ('random_hex should refuse length smaller 1', () => {
expect (
() => (crypto.random_hex (0))
)
.toThrowError ('invalid length');
});
it ('random_hex should always return correct length', () => {
for (let i = 1; i < 32; i++) {
const hex = crypto.random_hex (i);
expect (hex.length)
.toEqual (i);
}
});
it ('random_string', () => {
const str = crypto.random_string (16);
expect (str.length)
.toEqual (16);
});
it ('random_string with default length', () => {
const str = crypto.random_string ();
expect (str.length)
.toEqual (8);
});
it ('random_string should refuse length smaller 1', () => {
expect (
() => (crypto.random_string (0))
)
.toThrowError ('invalid length');
});
it ('random_string should always return correct length', () => {
for (let i = 1; i < 32; i++) {
const str = crypto.random_string (i);
expect (str.length)
.toEqual (i);
}
});
it ('create_salt', () => {
const salt = crypto.create_salt ();
expect (salt.length)
.toEqual (64);
expect (salt)
.toMatch (/^[0-9a-f]+$/iu);
});
});

133
test/spec/rsa.js Normal file
View File

@ -0,0 +1,133 @@
'use strict';
const crypto = require ('../../index');
const key_length = 512;
// eslint-disable-next-line max-lines-per-function
describe ('rsa', () => {
it ('should create a keypair', async () => {
const k = await crypto.generate_keypair ();
expect (k.private_key)
.toMatch (/^-----BEGIN PRIVATE KEY-----.+-----END PRIVATE KEY-----\n$/su);
expect (k.public_key)
// eslint-disable-next-line max-len
.toMatch (/^-----BEGIN RSA PUBLIC KEY-----.+-----END RSA PUBLIC KEY-----\n$/su);
});
it ('should throw on too small key size', async () => {
await expectAsync (crypto.generate_keypair (16))
.toBeRejectedWithError (
/key size too small/ui
);
});
it ('should encrypt and decrypt', async () => {
const k = await crypto.generate_keypair (key_length);
const data = 'foobar';
const enc = crypto.asym_encrypt (data, k.public_key);
const dec = crypto.asym_decrypt (enc, k.private_key);
expect (dec)
.toEqual (data);
expect (enc).not.toEqual (data);
});
it ('should throw if encryption key is invalid', () => {
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
+ 'MEgCCQDGvKLaq1SB/BTzocR4ZqGNr8dz1ylxxUpDncCu0C/ayOGPnCilB0LGEdqK\n'
+ 'rORlsYpIaDvLv6x6k8iSg6A/TsybAgMBAAE=\n'
+ '-----END RSA PUBLIC KEY-----\n';
const data = 'foobar';
expect (() => crypto.asym_encrypt (data, key))
.toThrow ();
});
it ('should throw on invalid decryption key', () => {
const data = 'EHsGr2eSVqZKTX1U9Qj4crGRFkk299kmxhiiO3fyaIS'
+ 'olB9+UYDdqrwcf/INwNddW4AzjA2Kf4dYaXIRLZsU1Q==';
const key = '-----BEGIN PRIVATE KEY-----\n'
+ 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxryi2qtUgfwU86HE\n'
+ 'eGahja/Hc9cpccVKQ53ArtXv2sjhj5wopQdCxhHaiqzkZbGKSGg7y7+sepPIkoOg\n'
+ 'P07MmwIDAQABAkBKIk3hoi24+07ZfwuqGibDksGlLarxHLZSOMOKsnBXfRRyGqMr\n'
+ '/Z+qtQ9VPRWHBzGHZ9rXAVKa8gnRirik+ez5AiEA/XL6tOy92Yvxm46ewswmZ7ab\n'
+ 'V1KvChsXziaRj5eLzacCIQDIvLBO8og5Ng4r7E/dOAYrvzOLFqlN5UCuCRZzuFpv\n'
+ '7QIgCnj5ywgNQDP8I8Vc4ge1fouZF56fBPfhn+8QDLLiX/kCIHewKd+otJiIJoMB\n'
+ '78yTLvq+klkINgKAAsTCHmT5MtMxAiEAkFE70ms8C73JvTkd0znq8H6fBJV0iZxQ\n'
+ 'qOXON8bzv8A=\n'
+ '-----END PRIVATE KEY-----\n';
expect (() => crypto.asym_decrypt (data, key))
.toThrow ();
});
it ('should throw on invalid decryption data', () => {
const data = 'EHsGr2eSkqZKTX1U9Qj4crGRFkk299kmxhiiO3fyaIS'
+ 'olB9+UYDdqrwcf/INwNddW4AzjA2Kf4dYaXIRLZsU1Q==';
const key = '-----BEGIN PRIVATE KEY-----\n'
+ 'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAxryi2qtUgfwU86HE\n'
+ 'eGahja/Hc9cpccVKQ53ArtAv2sjhj5wopQdCxhHaiqzkZbGKSGg7y7+sepPIkoOg\n'
+ 'P07MmwIDAQABAkBKIk3hoi24+07ZfwuqGibDksGlLarxHLZSOMOKsnBXfRRyGqMr\n'
+ '/Z+qtQ9VPRWHBzGHZ9rXAVKa8gnRirik+ez5AiEA/XL6tOy92Yvxm46ewswmZ7ab\n'
+ 'V1KvChsXziaRj5eLzacCIQDIvLBO8og5Ng4r7E/dOAYrvzOLFqlN5UCuCRZzuFpv\n'
+ '7QIgCnj5ywgNQDP8I8Vc4ge1fouZF56fBPfhn+8QDLLiX/kCIHewKd+otJiIJoMB\n'
+ '78yTLvq+klkINgKAAsTCHmT5MtMxAiEAkFE70ms8C73JvTkd0znq8H6fBJV0iZxQ\n'
+ 'qOXON8bzv8A=\n'
+ '-----END PRIVATE KEY-----\n';
expect (() => crypto.asym_decrypt (data, key))
.toThrow ();
});
it ('should create a signature and verify it', async () => {
const data = 'foobar';
const k = await crypto.generate_keypair (key_length);
const signature = crypto.asym_sign (data, k.private_key);
expect (typeof signature)
.toEqual ('string');
expect (signature)
.toMatch (/^[a-f0-9]+$/ui);
expect (crypto.asym_verify (data, k.public_key, signature))
.toBeTrue ();
});
it ('should throw on invalid sign key', () => {
const key = '-----BEGIN PRIVATE KEY-----\n'
+ 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEAl4RZveQ8IXHPZTUf\n'
+ 'djwgDxIB5444yFvhIRasdasdasdApz+FmsXxGCatnPUCIHO9P1EfxasdaIq/Lpng\n'
+ 'y2ZQh2IaDYxC7K7tvGZwSY/CEcGfAiAcNgsg5ZMIQOWxn2KbFM81ne3b5FzD3Ozh\n'
+ 'GxItcBfKuw==\n'
+ '-----END PRIVATE KEY-----\n';
const data = 'foobar';
expect (() => crypto.asym_sign (data, key))
.toThrow ();
});
it ('should throw on invalid verify key', () => {
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
+ 'MEgCQQCXhFm95DwDcc9lNasdasdasdasdasdHhEqigZupsWUxX/3tKn9tvEKV/\n'
+ 'zjoENWN63aorN+O+5MxDZMnEk+z9AgMBAAE=\n'
+ '-----END RSA PUBLIC KEY-----\n';
const data = 'foobar';
const signature = '3433ef165d6be80430e1107be0d7183bee769dbb38ea7d2737'
+ '1429c853fcab78bf1d3256fc16ee93a38cfd4ae79a74748e59fe9e9a65400c720d'
+ 'adb2dbcc1fa3';
expect (() => crypto.asym_verify (data, key, signature))
.toThrow ();
});
it ('should not throw on invalid data or signature', () => {
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
+ 'MEgCQQC+9nw80cG+AsZ2euIZx4ptmUykJgEUgs4JiEvC+IaiIRAd9zGc0TcQAeND\n'
+ '171lw77kE02KJ4ARl1hkcLCW2bIrAgMBAAE=\n'
+ '-----END RSA PUBLIC KEY-----\n';
const data = 'foobar';
const signature = '6bae51b0449d71ca2e04099268bd0f6506a5dfc6f810bd72d'
+ '47865574a99910e404e5856da650cd45ee88365a2511fcc0866a0b5d1faf15c067'
+ 'ab8a4427554bf';
expect (crypto.asym_verify (data, key, signature))
.toBeTrue ();
expect (crypto.asym_verify (data.replace ('b', 'c'), key, signature))
.toBeFalse ();
expect (crypto.asym_verify (data, key, signature.replace ('f', 'e')))
.toBeFalse ();
});
});

262
test/spec/signatures.js Normal file
View File

@ -0,0 +1,262 @@
'use strict';
const crypto = require ('../../index');
const rsa = require ('../../lib/rsa');
// eslint-disable-next-line max-lines-per-function
describe ('signatures', () => {
beforeEach (() => {
jasmine.clock ()
.install ();
const base_time = (new Date);
jasmine.clock ()
.mockDate (base_time);
});
afterEach (() => {
jasmine.clock ()
.uninstall ();
});
it ('sign_object', async () => {
const obj = { foo: 'bar' };
await expectAsync ((async () => {
const str = await crypto.sign_object (obj, 'baz');
expect (typeof str)
.toEqual ('string');
}) ())
.toBeResolved ();
});
it ('sign_object with rsa key', async () => {
const obj = { foo: 'bar' };
const k = await rsa.generate_keypair ();
await expectAsync ((async () => {
const str = await crypto.sign_object (obj, k.private_key);
expect (typeof str)
.toEqual ('string');
}) ())
.toBeResolved ();
});
it ('verify_signature with rsa key', async () => {
const obj = { foo: 'bar' };
const str = 'U1GcsN3yZzSKxPH8jhCGTKiswfazB9rMfUtE5351LT11t6EmS7xfPjnt'
+ '.5ytniC6q2ovoF7ZqbD8qk9r2kjjAcA9EYhLwC3wwJKPPsKdHSTFd7d9TzBP1skQ98X'
+ 'LjRUkc2M8M84LmWLg76EvcY2pw6HwsFvCUoZYcKAJp3vkp9MQVrVYdHKMPkBjQKyy2V'
+ 'KtEZiBsomBVJd6Hudd1YLMQ4J4s52iHsegDswKE9djYVEmgKkJUAiZJ2viFHw3fBbp2'
+ 'Abo2Dm5oqYtw7Nn9RFstW3CcNQHV1PzHDKD56Uw3opuYwVZhQth8ux2CdkC2yMvgVsT'
+ 'dUyCuu78ugaGvzsMXCbe2BzaPFDTE9JYtMcDFFP43nUGHNd6cWwzoKTZBX852Exz6Rb'
+ 'VjcWUvL81dLPBLJV.2';
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
+ 'MIIBCgKCAQEA4LCEoJYNwwksuzPESpmPziHp98WhY5Qml6RiN9uxrKGPV6QwwmDQ\n'
+ 'ks6C+ZfYbFG9NCx1MEuWL0Tvp/6ZBhMyaJrI5iwo0CmSX3WdFcbXmdl0l6N1+5r7\n'
+ 'l3SkKsr/AX4gwcDor4dYuLEv5KawGdfcP0IxsoAcIN1UJ5HJ+eheB3fVcSh/IIBf\n'
+ 'O+cL/4Chw8eAaDBG5mZ1Xgd4gIjJGYAxgUNvaShGzs8k1y+jqjD5IkZ1h9dgoGJG\n'
+ 'dUmjCLWrOzx8SqdqJYmQJX+6GNswnvVF30bkW+/MJZF/P2jLFtSa24Monh7axIqx\n'
+ '8HG0xDw1Z98WV9oQh/vDP/KAs1cPp0AJlwIDAQAB\n'
+ '-----END RSA PUBLIC KEY-----\n';
const ver = await crypto.verify_signature (str, key);
expect (ver)
.toEqual (obj);
});
it ('verify_signature reject with rsa key', async () => {
const str = 'U1GcsN3yZzSKxPH8jhCGTKiswfazB9rMfUtE5351LT11t6EmS7xfPjnt'
+ '.5ytniC6q2ovoF7ZqbD8qk9r2kjjAcA9EYhLwC3wwJKPPsKdHSTFd7d9TzBP1skQ98X'
+ 'LjRUkc2M8M84LmWLg76EvcY2pw6HwsFvCUoZYcKAJp3vkp9MQVrVYdHKMPkBjQKyy2V'
+ 'KtEZiBsomBVJd6Hudd1YLMQ4J4s52iHsegDswKE9djYVEmgKkJUAiZJ2viFHw3fBbp2'
+ 'Abo2Dm5oqYtw7Nn9RFstW3CcNQHV1PzHDKD56Uw3opuYwVZhQth8ux2CdkC2yMvgVsT'
+ 'dUyCuu78ugaGvzsMXCbe2BzaPFDTE9JYtMcDFFP43nUGHNd6cWwzoKTZBX852Exz6Rb'
+ 'VjcWUvL81dLPBLJA.2';
const key = '-----BEGIN RSA PUBLIC KEY-----\n'
+ 'MIIBCgKCAQEA4LCEoJYNwwksuzPESpmPziHp98WhY5Qml6RiN9uxrKGPV6QwwmDQ\n'
+ 'ks6C+ZfYbFG9NCx1MEuWL0Tvp/6ZBhMyaJrI5iwo0CmSX3WdFcbXmdl0l6N1+5r7\n'
+ 'l3SkKsr/AX4gwcDor4dYuLEv5KawGdfcP0IxsoAcIN1UJ5HJ+eheB3fVcSh/IIBf\n'
+ 'O+cL/4Chw8eAaDBG5mZ1Xgd4gIjJGYAxgUNvaShGzs8k1y+jqjD5IkZ1h9dgoGJG\n'
+ 'dUmjCLWrOzx8SqdqJYmQJX+6GNswnvVF30bkW+/MJZF/P2jLFtSa24Monh7axIqx\n'
+ '8HG0xDw1Z98WV9oQh/vDP/KAs1cPp0AJlwIDAQAB\n'
+ '-----END RSA PUBLIC KEY-----\n';
const ver = await crypto.verify_signature (str, key);
expect (ver)
.toBeNull ();
});
it ('should sign object with key info', async () => {
const obj = { foo: 'bar' };
await expectAsync ((async () => {
const str = crypto.sign_object (obj, 'baz', 'baz');
const res = await crypto.get_signature_info (str);
expect (res.key_info)
.toEqual ('baz');
}) ())
.toBeResolved ();
});
it ('should sign object with custom properties', async () => {
const obj = { foo: 'bar' };
await expectAsync ((async () => {
const str = crypto.sign_object (obj, 'baz', { bar: 'baz' });
const res = await crypto.get_signature_info (str);
expect (res.bar)
.toEqual ('baz');
}) ())
.toBeResolved ();
});
it ('should sign object with custom override properties', async () => {
const obj = { foo: 'bar' };
await expectAsync ((async () => {
const str = crypto.sign_object (obj, 'baz', { iat: 'baz' });
const res = await crypto.get_signature_info (str);
expect (res.iat)
.toEqual ('baz');
}) ())
.toBeResolved ();
});
it ('decode_signed', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = await crypto.decode_signed (str);
expect (obj)
.toEqual (dec);
});
it ('verify_signature', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = await crypto.verify_signature (str, 'baz');
expect (obj)
.toEqual (dec);
});
it ('should verify and return all info', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz', { iat: 'baz' });
const dec = await crypto.verify_signature_get_info (str, 'baz');
expect (dec.obj)
.toEqual (obj);
expect (dec.iat)
.toEqual ('baz');
});
it ('should verify signature using function retrieved key', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = await crypto.verify_signature (str, () => 'baz');
expect (obj)
.toEqual (dec);
});
it (
'should verify signature using function retrieved timeout 0',
async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = await crypto.verify_signature (str, 'baz', () => 0);
expect (obj)
.toEqual (dec);
}
);
it ('should reject tampered signatures', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
const dec = await crypto.verify_signature (str, 'foo');
expect (dec)
.toEqual (null);
});
it ('should return null on invalid input', async () => {
const ver = await crypto.verify_signature (null, 'foo');
expect (ver)
.toEqual (null);
const dec = await crypto.decode_signed (null);
expect (dec)
.toEqual (null);
});
it ('should not fail verification if timeout unspecified', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
jasmine.clock ()
.tick (36e5);
const dec = await crypto.verify_signature (str, 'baz');
expect (obj)
.toEqual (dec);
});
it ('should reject old signatures', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
jasmine.clock ()
.tick (50);
const dec = await crypto.verify_signature (str, 'baz', 1);
expect (dec)
.toEqual (null);
});
it ('should not reject valid signatures', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
jasmine.clock ()
.tick (50);
const dec = await crypto.verify_signature (str, 'baz', 100);
expect (obj)
.toEqual (dec);
});
it ('should verify signature using function retrieved timeout', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz', { to: 100 });
jasmine.clock ()
.tick (50);
const dec = await crypto.verify_signature (str, 'baz', (info) => info.to);
expect (obj)
.toEqual (dec);
});
it ('verify_signature on almost timed out packet', async () => {
const obj = { foo: 'bar' };
const str = crypto.sign_object (obj, 'baz');
jasmine.clock ()
.tick (10);
const dec = await crypto.verify_signature (str, 'baz', 10);
expect (obj)
.toEqual (dec);
});
it ('should decode problematic token', async () => {
// eslint-disable-next-line max-len
const str = 'wEJbzvUywiaiGWZUG6CtCXNkNmRGyVoi9icytpTe4gZhsb8Gk.5PZbhGL525mdV7EmYomTwUei6qULpLaZwSXy92eaUDNgbyXPHsr9dfUCeEBpTqmzuq3VtmmV43epUyWRoHocAsV3.2';
const obj = await crypto.decode_signed (str);
expect (obj)
.toEqual ({ id: 1 });
});
it ('should automatically reencode b64 tokens', async () => {
// eslint-disable-next-line max-len
const str = 'eyJpYXQiOjE1ODE0NDAwMTIyODgsIm9iaiI6eyJpZCI6MX19.24ZOsWrnfkNe%2FbM0r7DaVJMqE2bfn2aAM%2BZSzWeSf31OCTlXXNWD34RBL2X5v3UliYQ4IIsLNBFbaW9texPHug%3D%3D';
const obj = await crypto.decode_signed (str);
expect (obj)
.toEqual ({ id: 1 });
});
it ('verify_signature on b64 string', async () => {
// eslint-disable-next-line max-len
const str = 'eyJpYXQiOjE2MDkzNDQ4MDMyMjcsIm9iaiI6eyJpZCI6MX19.N762xuMaNbT%2Fqb0uTKST68BZgSnmNxXaHl4GY7iAKqaDDEwZn3biYfg5DgJ45QgPZrndchczDjUqLkyXoqw4KQ%3D%3D';
const obj = await crypto.verify_signature (str, 'baz');
expect (obj)
.toEqual ({ id: 1 });
});
});

5961
yarn.lock

File diff suppressed because it is too large Load Diff