Initial commit

This commit is contained in:
2025-03-07 19:22:02 +01:00
commit 4a98255d83
55743 changed files with 5280367 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Maurice Butler
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 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.
+55
View File
@@ -0,0 +1,55 @@
# cryptr
cryptr is a simple `aes-256-gcm` encrypt and decrypt module for node.js
It is for doing simple encryption of values UTF-8 strings that need to be decrypted at a later time.
If you require anything more than that you probably want to use something more advanced or [crypto](https://nodejs.org/api/crypto.html) directly.
The Cryptr constructor takes 1 required argument, and an optional options object.
`Cryptr(secret[, options])`
- secret: `<string>`
- options: `<Object>`
- encoding: `<string>` Defaults to 'hex' (see [Node.js Buffer documentation] for valid options)
- pbkdf2Iterations: `<number>` Defaults to 100000
- saltLength: `<number>` Defaults to 64
The `salt` and `iv` are randomly generated and prepended to the result.
**DO NOT USE THIS MODULE FOR ENCRYPTING PASSWORDS!**
Passwords should be a one way hash. Use [bcrypt](https://npmjs.org/package/bcrypt) for that.
## Install
`npm install cryptr`
## Usage
```javascript
const Cryptr = require('cryptr');
const cryptr = new Cryptr('myTotallySecretKey');
const encryptedString = cryptr.encrypt('bacon');
const decryptedString = cryptr.decrypt(encryptedString);
console.log(encryptedString); // 2a3260f5ac4754b8ee3021ad413ddbc11f04138d01fe0c5889a0dd7b4a97e342a4f43bb43f3c83033626a76f7ace2479705ec7579e4c151f2e2196455be09b29bfc9055f82cdc92a1fe735825af1f75cfb9c94ad765c06a8abe9668fca5c42d45a7ec233f0
console.log(decryptedString); // bacon
```
#### With Options
```javascript
const Cryptr = require('cryptr');
const cryptr = new Cryptr('myTotallySecretKey', { encoding: 'base64', pbkdf2Iterations: 10000, saltLength: 10 });
const encryptedString = cryptr.encrypt('bacon');
const decryptedString = cryptr.decrypt(encryptedString);
console.log(encryptedString); // CPbKO/FFLQ8lVKxV+jYJcLcpTU0ZvW3D+JVfUecmJmLYY10UxYEa/wf8PWDQqhw=
console.log(decryptedString); // bacon
```
[Node.js Buffer documentation]: https://nodejs.org/api/buffer.html#buffers-and-character-encodings
+12
View File
@@ -0,0 +1,12 @@
declare class Cryptr {
constructor(
secret: string,
options?: { pbkdf2Iterations?: number; saltLength?: number; encoding?: 'hex' | 'base64' | 'latin1' },
);
encrypt(value: string): string;
decrypt(value: string): string;
}
export = Cryptr;
+80
View File
@@ -0,0 +1,80 @@
const crypto = require('crypto');
const algorithm = 'aes-256-gcm';
const ivLength = 16;
const tagLength = 16;
const defaultEncoding = 'hex';
const defaultSaltLength = 64;
const defaultPbkdf2Iterations = 100000;
function Cryptr(secret, options) {
if (!secret || typeof secret !== 'string') {
throw new Error('Cryptr: secret must be a non-0-length string');
}
let encoding = defaultEncoding;
let saltLength = defaultSaltLength;
let pbkdf2Iterations = defaultPbkdf2Iterations;
if (options) {
if (options.encoding) {
encoding = options.encoding;
}
if (options.pbkdf2Iterations) {
pbkdf2Iterations = options.pbkdf2Iterations;
}
if (options.saltLength) {
saltLength = options.saltLength;
}
}
const tagPosition = saltLength + ivLength;
const encryptedPosition = tagPosition + tagLength;
function getKey(salt) {
return crypto.pbkdf2Sync(secret, salt, pbkdf2Iterations, 32, 'sha512');
}
this.encrypt = function encrypt(value) {
if (value == null) {
throw new Error('value must not be null or undefined');
}
const iv = crypto.randomBytes(ivLength);
const salt = crypto.randomBytes(saltLength);
const key = getKey(salt);
const cipher = crypto.createCipheriv(algorithm, key, iv);
const encrypted = Buffer.concat([cipher.update(String(value), 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
return Buffer.concat([salt, iv, tag, encrypted]).toString(encoding);
};
this.decrypt = function decrypt(value) {
if (value == null) {
throw new Error('value must not be null or undefined');
}
const stringValue = Buffer.from(String(value), encoding);
const salt = stringValue.subarray(0, saltLength);
const iv = stringValue.subarray(saltLength, tagPosition);
const tag = stringValue.subarray(tagPosition, encryptedPosition);
const encrypted = stringValue.subarray(encryptedPosition);
const key = getKey(salt);
const decipher = crypto.createDecipheriv(algorithm, key, iv);
decipher.setAuthTag(tag);
return decipher.update(encrypted) + decipher.final('utf8');
};
}
module.exports = Cryptr;
+41
View File
@@ -0,0 +1,41 @@
{
"name": "cryptr",
"version": "6.3.0",
"description": "a simple encrypt and decrypt module for node.js",
"main": "index.js",
"types": "index.d.ts",
"directories": {
"test": "tests"
},
"scripts": {
"test": "node ./tests"
},
"repository": {
"type": "git",
"url": "git://github.com/MauriceButler/cryptr.git"
},
"keywords": [
"cryptr",
"crypter",
"encrypt",
"decrypt",
"encryption",
"decryption",
"crypto",
"cipher",
"aes-256",
"aes256",
"aes-256-ctr",
"aes-256-gcm",
"hashr"
],
"author": "Maurice Butler <maurice.butler@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/MauriceButler/cryptr/issues"
},
"homepage": "https://github.com/MauriceButler/cryptr",
"devDependencies": {
"tape": "^5.6.1"
}
}
+180
View File
@@ -0,0 +1,180 @@
const test = require('tape');
const Cryptr = require('../');
const testSecret = 'myTotalySecretKey';
const testData = 'bacon';
test('works...', (t) => {
t.plan(1);
const cryptr = new Cryptr(testSecret);
const encryptedString = cryptr.encrypt(testData);
const decryptedString = cryptr.decrypt(encryptedString);
t.equal(decryptedString, testData, 'decrypted aes256 correctly');
});
test('works with custom encoding', (t) => {
const encodings = ['hex', 'base64', 'latin1'];
t.plan(encodings.length);
encodings.forEach((encoding) => {
const cryptr = new Cryptr(testSecret, { encoding });
const encryptedString = cryptr.encrypt(testData);
const decryptedString = cryptr.decrypt(encryptedString);
t.equal(decryptedString, testData, `decrypted correctly with ${encoding} encoding`);
});
});
test('custom encoding affects output length', (t) => {
t.plan(1);
const cryptr = new Cryptr(testSecret, { encoding: 'base64' });
const cryptr2 = new Cryptr(testSecret);
const encryptedString = cryptr.encrypt(testData);
const encryptedString2 = cryptr2.encrypt(testData);
t.ok(encryptedString.length < encryptedString2.length, 'custom encoding was shorter');
});
test('works with custom pbkdf2Iterations', (t) => {
t.plan(1);
const cryptr = new Cryptr(testSecret, { pbkdf2Iterations: 10000 });
const encryptedString = cryptr.encrypt(testData);
const decryptedString = cryptr.decrypt(encryptedString);
t.equal(decryptedString, testData, 'decrypted aes256 correctly with custom iterations');
});
test('custom pbkdf2Iterations affects speed', (t) => {
t.plan(1);
const cryptr = new Cryptr(testSecret, { pbkdf2Iterations: 1000 });
const cryptr2 = new Cryptr(testSecret);
const customStart = performance.now();
for (let index = 0; index < 10; index++) {
const encryptedString = cryptr.encrypt(testData + index);
cryptr.decrypt(encryptedString);
}
const customEnd = performance.now();
const defaultStart = performance.now();
for (let index = 0; index < 10; index++) {
const encryptedString = cryptr2.encrypt(testData + index);
cryptr2.decrypt(encryptedString);
}
const defaultEnd = performance.now();
const customTime = customEnd - customStart;
const defaultTime = defaultEnd - defaultStart;
t.ok(customTime < defaultTime, 'custom iterations were faster');
});
test('works with custom saltLength', (t) => {
t.plan(1);
const cryptr = new Cryptr(testSecret, { saltLength: 10 });
const encryptedString = cryptr.encrypt(testData);
const decryptedString = cryptr.decrypt(encryptedString);
t.equal(decryptedString, testData, 'decrypted aes256 correctly with custom salt length');
});
test('custom saltLength affects output length', (t) => {
t.plan(1);
const customSaltLength = 30;
const cryptr = new Cryptr(testSecret, { saltLength: customSaltLength });
const cryptr2 = new Cryptr(testSecret);
const encryptedString = cryptr.encrypt(testData);
const encryptedString2 = cryptr2.encrypt(testData);
t.equal(
encryptedString2.length - encryptedString.length,
(64 - customSaltLength) * 2,
'output length is affected by salt length',
);
});
test('works with utf8 specific characters', (t) => {
t.plan(1);
const testString = 'ßáÇÖÑ 🥓';
const cryptr = new Cryptr(testSecret);
const encryptedString = cryptr.encrypt(testString);
const decryptedString = cryptr.decrypt(encryptedString);
t.equal(decryptedString, testString, 'decrypted aes256 correctly with UTF8 chars');
});
test('goes bang if bad secret', (t) => {
const badSecrets = [null, undefined, 0, 123451345134, '', Buffer.from('buffer'), {}];
t.plan(badSecrets.length);
for (let i = 0; i < badSecrets.length; i++) {
t.throws(
() => new Cryptr(badSecrets[i]),
/Cryptr: secret must be a non-0-length string/,
`throws on bad secret ${badSecrets[i]}`,
);
}
});
test('encrypt goes bang if value is null or undefined', (t) => {
const cryptr = new Cryptr(testSecret);
const badValues = [null, undefined];
t.plan(badValues.length);
for (let i = 0; i < badValues.length; i++) {
t.throws(
() => cryptr.encrypt(badValues[i]),
/value must not be null or undefined/,
`throws on value ${badValues[i]}`,
);
}
});
test('decrypt goes bang if value is null or undefined', (t) => {
const cryptr = new Cryptr(testSecret);
const badValues = [null, undefined];
t.plan(badValues.length);
for (let i = 0; i < badValues.length; i++) {
t.throws(
() => cryptr.decrypt(badValues[i]),
/value must not be null or undefined/,
`throws on value ${badValues[i]}`,
);
}
});
test('decrypt goes bang if value has been tampered with', (t) => {
t.plan(1);
const cryptr = new Cryptr(testSecret);
const encryptedString = cryptr.encrypt(testData);
const encryptedBuffer = Buffer.from(encryptedString, 'hex');
const b1 = Buffer.from(testData);
const b2 = Buffer.from('hello');
for (let i = 0; i < b1.length; i++) {
encryptedBuffer[i + 16] ^= b1[i] ^ b2[i];
}
const modifiedValue = encryptedBuffer.toString('hex');
t.throws(
() => cryptr.decrypt(modifiedValue),
/Unsupported state or unable to authenticate data/,
'throws on tampered data',
);
});