refactoring, asymmetric signatures and encryption
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Timo Hocker 2021-01-06 15:13:04 +01:00
parent 612686a224
commit ecd0195aee
14 changed files with 749 additions and 392 deletions

View File

@ -1,5 +1,11 @@
# CHANGELOG
## 1.3.0
asymmetric encryption and signatures
object signing: ability to use rsa keys
## 1.2.0
object signing:

View File

@ -1,6 +1,6 @@
# @sapphirecode/crypto-helper
version: 1.2.x
version: 1.3.x
simple functions for cryptography
@ -16,6 +16,8 @@ yarn:
## Usage
### Examples
```js
const crypto = require('@sapphirecode/crypto-helper');
@ -31,13 +33,34 @@ const info = crypto.get_signature_info(signed); // returns an object with iat (i
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 ver_info = crypto.verify_signature_get_info(signed, 'secret', 10000); // verify a signature and get signature information like iat and key_info
const ver_func = crypto.verify_signature(signed, (signature_info)=>'secret', 10000); // verify a signature, retrieve the key using the signature info
const ver_func = 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>

297
index.js
View File

@ -8,293 +8,16 @@
// @ts-nocheck
'use strict';
const crypto = require ('crypto');
const encoding = require ('@sapphirecode/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_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
};
/**
* 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);
}
/**
* 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|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 token = encoding.to_b58 (hash_sha512 (str, key), 'hex');
const res = `${str}.${token}.2`;
return res;
}
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 : key (res.json);
res.hash = 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} key used key
* @param {number|(Object)=>number} timeout timeout (optional)
* @returns {any} returns object if successful, else null
*/
function verify_signature_get_info (str, key, timeout = 0) {
if (typeof str !== 'string')
return null;
const { json, token, hash } = parse_signature (str, key);
if (token !== hash)
return null;
const time = Date.now () - json.iat;
const num_timeout = typeof timeout === 'number' ? timeout : 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} key used key
* @param {number|(Object)=>number} timeout timeout (optional)
* @returns {any} returns object if successful, else null
*/
function verify_signature (str, key, timeout = 0) {
const res = 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 {any} data
*/
function get_signature_info (str) {
if (typeof str !== 'string')
return null;
const { json } = parse_signature (str);
return json;
}
/**
* decode a signed object without verifying the signature
*
* @param {string} str string to decode
* @returns {any} object
*/
function decode_signed (str) {
const info = get_signature_info (str);
if (info)
return info.obj;
return null;
}
/**
* 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),
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;
}
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');
module.exports = {
checksum,
create_salt,
decode_signed,
decrypt_aes,
encrypt_aes,
encryption_mode_cbc_128,
encryption_mode_cbc_256,
encryption_mode_cbc_256_quick,
get_signature_info,
hash_sha512,
random_hex,
random_string,
sign_object,
verify_signature,
verify_signature_get_info
...random,
...hashing,
...encryption,
...signatures,
...rsa
};

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
};

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
};

126
lib/signatures.js Normal file
View File

@ -0,0 +1,126 @@
'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;
}
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 : 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} key used key
* @param {number|(Object)=>number} timeout timeout (optional)
* @returns {any} returns object if successful, else null
*/
function verify_signature_get_info (str, key, timeout = 0) {
if (typeof str !== 'string')
return null;
const { json, token, hash, is_rsa } = 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 : 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} key used key
* @param {number|(Object)=>number} timeout timeout (optional)
* @returns {any} returns object if successful, else null
*/
function verify_signature (str, key, timeout = 0) {
const res = 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 {any} data
*/
function get_signature_info (str) {
if (typeof str !== 'string')
return null;
const { json } = parse_signature (str);
return json;
}
/**
* decode a signed object without verifying the signature
*
* @param {string} str string to decode
* @returns {any} object
*/
function decode_signed (str) {
const info = 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,6 +1,6 @@
{
"name": "@sapphirecode/crypto-helper",
"version": "1.2.2",
"version": "1.3.0",
"main": "index.js",
"author": {
"name": "Timo Hocker",
@ -38,10 +38,12 @@
},
"files": [
"LICENSE",
"lib/*.js",
"lib/*.d.ts",
"index.js",
"index.d.ts"
],
"engines": {
"node": ">=10.0.0"
"node": ">=10.12.0"
}
}

View File

@ -19,5 +19,5 @@ module.exports = {
testRunner: 'jasmine',
jasmineConfigFile: 'jasmine.json',
coverageAnalysis: 'perTest',
mutate: [ 'index.js' ]
mutate: [ 'lib/*.js' ]
};

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'
);
});
});

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 (
'error:0408F078:rsa routines:pkey_rsa_ctrl:key size too small'
);
});
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 ();
});
});

View File

@ -1,17 +1,10 @@
/*
* 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');
const rsa = require ('../../lib/rsa');
// eslint-disable-next-line max-lines-per-function, max-statements
describe ('crypto helper', () => {
// eslint-disable-next-line max-lines-per-function
describe ('signatures', () => {
beforeEach (() => {
jasmine.clock ()
.install ();
@ -25,95 +18,6 @@ describe ('crypto helper', () => {
.uninstall ();
});
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 ('hash_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'
);
});
it ('create_salt', () => {
const salt = crypto.create_salt ();
expect (salt.length)
.toEqual (64);
expect (salt)
.toMatch (/^[0-9a-f]+$/iu);
});
it ('sign_object', () => {
const obj = { foo: 'bar' };
expect (() => {
@ -123,6 +27,59 @@ describe ('crypto helper', () => {
}).not.toThrow ();
});
it ('sign_object with rsa key', async () => {
const obj = { foo: 'bar' };
const k = await rsa.generate_keypair ();
expect (() => {
const str = crypto.sign_object (obj, k.private_key);
expect (typeof str)
.toEqual ('string');
}).not.toThrow ();
});
it ('verify_signature with rsa key', () => {
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 = crypto.verify_signature (str, key);
expect (ver)
.toEqual (obj);
});
it ('verify_signature reject with rsa key', () => {
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 = crypto.verify_signature (str, key);
expect (ver)
.toBeNull ();
});
it ('should sign object with key info', () => {
const obj = { foo: 'bar' };
expect (() => {