mirror of
https://github.com/cupcakearmy/occulto.git
synced 2024-10-31 22:04:11 +01:00
initial commit
This commit is contained in:
commit
c1e4f4b1aa
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
package-lock.json
|
||||
lib
|
||||
|
||||
.idea
|
2
.npmignore
Normal file
2
.npmignore
Normal file
@ -0,0 +1,2 @@
|
||||
*
|
||||
!lib/*
|
143
README.md
Normal file
143
README.md
Normal file
@ -0,0 +1,143 @@
|
||||
# occulto 🔒
|
||||
|
||||
High leven wrapper around [forge](https://github.com/digitalbazaar/forge).
|
||||
|
||||
**Typescript typings included**
|
||||
|
||||
## Quickstart 🚀
|
||||
|
||||
###### Install
|
||||
|
||||
```
|
||||
npm i occulto
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Whatever import you prefer
|
||||
// const { RSA } = require('occulto')
|
||||
import { RSA } from 'occulto'
|
||||
|
||||
const pair = await RSA.gen()
|
||||
|
||||
const encrypted = RSA.encrypt('some string', 'myPass')
|
||||
const decrypted = RSA.decrypt(encrypted, 'myPass')
|
||||
|
||||
```
|
||||
|
||||
### Reference 📒
|
||||
|
||||
## RSA
|
||||
|
||||
#### `RSA.gen(bits:? number)`
|
||||
|
||||
- bits: [optional, default=4096] Size of the RSA key
|
||||
|
||||
###### Examples
|
||||
|
||||
```javascript
|
||||
const pair = await RSA.gen() // 4096-Bit
|
||||
const smallPair = await RSA.gen(2**10) // 1024-Bit
|
||||
```
|
||||
|
||||
#### `.add(amount, interval)`
|
||||
|
||||
Adds a specified amount to an existing duration
|
||||
|
||||
###### Example
|
||||
|
||||
```javascript
|
||||
const a = new Duration(1, 'day')
|
||||
a.add(12, 'hours')
|
||||
a.asHour() // 36
|
||||
```
|
||||
|
||||
#### `.subtract(amount, interval)`
|
||||
|
||||
Subtracts a specified amount to an existing duration
|
||||
|
||||
###### Example
|
||||
|
||||
```javascript
|
||||
const a = new Duration(1, 'day')
|
||||
a.subtract(12, 'hours')
|
||||
a.asHour() // 12
|
||||
```
|
||||
|
||||
#### Getters
|
||||
|
||||
Gets the amount of time interval, not the total time
|
||||
|
||||
- `.milliseconds()`
|
||||
- `.seconds()`
|
||||
- `.minutes()`
|
||||
- `.hours()`
|
||||
- `.days()`
|
||||
- `.weeks()`
|
||||
- `.years()`
|
||||
|
||||
###### Example
|
||||
|
||||
```javascript
|
||||
const a = new Duration(1, 'day')
|
||||
a.days() // 1
|
||||
a.add(5, 'minutes')
|
||||
a.days() // 1
|
||||
a.add(1, 'year')
|
||||
a.days() // 1
|
||||
a.add(24, 'hours')
|
||||
a.days() // 2
|
||||
```
|
||||
|
||||
#### As interval
|
||||
|
||||
Calculates the time duration as a time interval.
|
||||
|
||||
- `.asMilliseconds()`
|
||||
- `.asSeconds()`
|
||||
- `.asMinutes()`
|
||||
- `.asHours()`
|
||||
- `.asDays()`
|
||||
- `.asWeeks()`
|
||||
- `.asYears()`
|
||||
|
||||
###### Example
|
||||
|
||||
```javascript
|
||||
const a = new Duration(1, 'day')
|
||||
a.asHours() // 24
|
||||
```
|
||||
|
||||
#### `.humanize()`
|
||||
|
||||
This functions takes a duration and tries to make a human readable version out of it.
|
||||
|
||||
###### Example
|
||||
|
||||
```javascript
|
||||
const a = new Duration(4, 'seconds')
|
||||
a.humanize() // 'a moment'
|
||||
a.add(5, 'minutes')
|
||||
a.humanize() // 'a few minutes'
|
||||
```
|
||||
|
||||
##### Own rules / i18n
|
||||
|
||||
If you want to pass a different humanize function you can.
|
||||
The order of the array is important. The first match will return, like in a standard server router. The first argument is a function that takes the duration and returns a boolean. The second takes also matched duration and returns a string for the user.
|
||||
|
||||
###### Example
|
||||
|
||||
```javascript
|
||||
const humanizer = [
|
||||
[d => d.days() > 1, d => `${d.days()} days`],
|
||||
[d => d.days() > 0, d => `1 day`],
|
||||
[() => true, () => 'catch all, below 1 day'],
|
||||
]
|
||||
|
||||
const a = new Duration(2, 'days')
|
||||
a.humanize(humanizer) // '2 days'
|
||||
a.subtract(1, 'day')
|
||||
a.humanize(humanizer) // '1 day'
|
||||
a.subtract(12, 'hours')
|
||||
a.humanize(humanizer) // 'catch all, below 1 day'
|
||||
```
|
28
package.json
Normal file
28
package.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "occulto",
|
||||
"version": "1.0.1",
|
||||
"description": "crypt utility",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsnd --respawn --no-notify src/index.ts",
|
||||
"test": "npm run build && mocha",
|
||||
"prepublish": "rm -r ./lib && npm run test"
|
||||
},
|
||||
"keywords": [
|
||||
""
|
||||
],
|
||||
"author": "Niccolo Borgioli",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^11.12.1",
|
||||
"@types/node-forge": "^0.8.2",
|
||||
"mocha": "^6",
|
||||
"ts-node-dev": "^1.0.0-pre.32",
|
||||
"typescript": "^3.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"node-forge": "^0.8.2"
|
||||
}
|
||||
}
|
164
src/AES.ts
Normal file
164
src/AES.ts
Normal file
@ -0,0 +1,164 @@
|
||||
import forge from 'node-forge'
|
||||
|
||||
export enum Ciphers {
|
||||
AES_256_GCM,
|
||||
AES_192_GCM,
|
||||
AES_128_GCM,
|
||||
AES_256_CTR,
|
||||
AES_192_CTR,
|
||||
AES_128_CTR,
|
||||
AES_256_CBC,
|
||||
AES_192_CBC,
|
||||
AES_128_CBC,
|
||||
}
|
||||
|
||||
type CipherConfig = {
|
||||
alg: forge.cipher.Algorithm,
|
||||
saltSize: number
|
||||
keySize: number
|
||||
tagSize: number
|
||||
it: number
|
||||
}
|
||||
|
||||
type EncryptedItem = {
|
||||
alg: forge.cipher.Algorithm,
|
||||
data: string,
|
||||
keySize: number,
|
||||
iv: string,
|
||||
it: number
|
||||
salt: string,
|
||||
tag: string,
|
||||
tagSize: number,
|
||||
}
|
||||
|
||||
export default class AES {
|
||||
|
||||
static Ciphers = Ciphers
|
||||
|
||||
static encrypt(data: string, key: string, type: Ciphers = Ciphers.AES_256_GCM): string {
|
||||
const { alg, it, keySize, saltSize, tagSize } = AES.getCipherConfig(type)
|
||||
const salt = forge.random.getBytesSync(saltSize)
|
||||
const iv = forge.random.getBytesSync(keySize)
|
||||
|
||||
const cipher = forge.cipher.createCipher(
|
||||
alg,
|
||||
forge.pkcs5.pbkdf2(key, salt, it, keySize),
|
||||
)
|
||||
|
||||
cipher.start({
|
||||
iv,
|
||||
tagLength: tagSize,
|
||||
})
|
||||
cipher.update(forge.util.createBuffer(data))
|
||||
cipher.finish()
|
||||
|
||||
const encrypted: EncryptedItem = {
|
||||
alg,
|
||||
data: forge.util.hexToBytes(cipher.output.toHex()),
|
||||
keySize,
|
||||
iv,
|
||||
it,
|
||||
salt,
|
||||
tag: forge.util.hexToBytes(cipher.mode.tag.toHex()),
|
||||
tagSize,
|
||||
}
|
||||
|
||||
return Buffer.from(JSON.stringify(encrypted)).toString('base64')
|
||||
}
|
||||
|
||||
static decrypt(e: string, key: string): string {
|
||||
const { alg, data, keySize, iv, it, salt, tag, tagSize }: EncryptedItem = JSON.parse(Buffer.from(e, 'base64').toString())
|
||||
|
||||
const cipher = forge.cipher.createCipher(
|
||||
alg,
|
||||
forge.pkcs5.pbkdf2(key, salt, it, keySize),
|
||||
)
|
||||
|
||||
cipher.start({
|
||||
iv,
|
||||
tag: new forge.util.ByteStringBuffer(tag),
|
||||
tagLength: tagSize,
|
||||
})
|
||||
cipher.update(new forge.util.ByteStringBuffer(data))
|
||||
cipher.finish()
|
||||
|
||||
return cipher.output.toString()
|
||||
}
|
||||
|
||||
private static getCipherConfig = (type: Ciphers): CipherConfig => {
|
||||
switch (type) {
|
||||
case Ciphers.AES_128_GCM:
|
||||
return {
|
||||
alg: 'AES-GCM',
|
||||
saltSize: 128,
|
||||
keySize: 16,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_192_GCM:
|
||||
return {
|
||||
alg: 'AES-GCM',
|
||||
saltSize: 128,
|
||||
keySize: 24,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_256_GCM:
|
||||
return {
|
||||
alg: 'AES-GCM',
|
||||
saltSize: 128,
|
||||
keySize: 32,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_128_CBC:
|
||||
return {
|
||||
alg: 'AES-CBC',
|
||||
saltSize: 128,
|
||||
keySize: 16,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_192_CBC:
|
||||
return {
|
||||
alg: 'AES-CBC',
|
||||
saltSize: 128,
|
||||
keySize: 24,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_256_CBC:
|
||||
return {
|
||||
alg: 'AES-CBC',
|
||||
saltSize: 128,
|
||||
keySize: 32,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_128_CTR:
|
||||
return {
|
||||
alg: 'AES-CTR',
|
||||
saltSize: 128,
|
||||
keySize: 16,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_192_CTR:
|
||||
return {
|
||||
alg: 'AES-CTR',
|
||||
saltSize: 128,
|
||||
keySize: 24,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
case Ciphers.AES_256_CTR:
|
||||
return {
|
||||
alg: 'AES-CTR',
|
||||
saltSize: 128,
|
||||
keySize: 32,
|
||||
tagSize: 128,
|
||||
it: 2 ** 12,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
39
src/RSA.ts
Normal file
39
src/RSA.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import forge from 'node-forge'
|
||||
import { Base64 } from './Util'
|
||||
|
||||
type PrivateKey = string
|
||||
type PublicKey = string
|
||||
|
||||
export type KeyPair = {
|
||||
pub: PublicKey
|
||||
prv: PrivateKey
|
||||
}
|
||||
|
||||
|
||||
export default class RSA {
|
||||
|
||||
static gen = (size: number = 2 ** 12): Promise<KeyPair> => new Promise<KeyPair>((resolve, reject) => {
|
||||
forge.pki.rsa.generateKeyPair({ bits: size }, function (err, keypair) {
|
||||
if (err) reject()
|
||||
else resolve({
|
||||
pub: forge.pki.publicKeyToPem(keypair.publicKey),
|
||||
prv: forge.pki.privateKeyToPem(keypair.privateKey),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
static encrypt(data: string, key: PublicKey): string {
|
||||
const obj = forge.pki.publicKeyFromPem(key)
|
||||
if (obj instanceof ArrayBuffer) throw new Error()
|
||||
|
||||
return Base64.encode(obj.encrypt(data))
|
||||
}
|
||||
|
||||
static decrypt(data: string, key: PrivateKey): string {
|
||||
const obj = forge.pki.privateKeyFromPem(key)
|
||||
if (obj instanceof ArrayBuffer) throw new Error()
|
||||
|
||||
return obj.decrypt(Base64.decode(data))
|
||||
}
|
||||
|
||||
}
|
4
src/Util.ts
Normal file
4
src/Util.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export class Base64 {
|
||||
static encode = (s: string): string => Buffer.from(s).toString('base64')
|
||||
static decode = (s: string): string => Buffer.from(s, 'base64').toString()
|
||||
}
|
27
src/index.ts
Normal file
27
src/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import AES from './AES'
|
||||
import RSA from './RSA'
|
||||
//
|
||||
// (async () => {
|
||||
//
|
||||
// const text = `lorem ipsums`
|
||||
// const key = `test`
|
||||
//
|
||||
// // Asymetric
|
||||
// const pair = await RSA.gen()
|
||||
// const ae = RSA.encrypt(text, pair.pub)
|
||||
// console.log(ae)
|
||||
// const ad = RSA.decrypt(ae, pair.prv)
|
||||
// console.log(ad)
|
||||
//
|
||||
// // Symmetric
|
||||
// const se = AES.encrypt(text, key, AES.Ciphers.AES_256_GCM)
|
||||
// console.log(se)
|
||||
// const sd = AES.decrypt(se, key)
|
||||
// console.log(sd)
|
||||
//
|
||||
// })()
|
||||
|
||||
export default {
|
||||
RSA,
|
||||
AES,
|
||||
}
|
64
test/main.js
Normal file
64
test/main.js
Normal file
@ -0,0 +1,64 @@
|
||||
const { describe, it } = require('mocha')
|
||||
const a = require('assert')
|
||||
|
||||
const { RSA, AES } = require('../').default
|
||||
|
||||
describe('Asymmetric', () => {
|
||||
|
||||
describe('RSA', () => {
|
||||
|
||||
it('Encrypt and Decrypt small string', async () => {
|
||||
const pair = await RSA.gen(2 ** 10)
|
||||
const text = `some small string`
|
||||
const e = RSA.encrypt(text, pair.pub)
|
||||
const d = RSA.decrypt(e, pair.prv)
|
||||
a.strictEqual(text, d)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('Symmetric', () => {
|
||||
|
||||
const keyLong = `yruyLaCAbcfJD9DKk3Ef6zuSFMPatwbg63ayPHVDSSxAePqtYxmd7N5BX2ShCgaG`
|
||||
const keyShort = `simple`
|
||||
const dataShort = `a sentence`
|
||||
const dataLong = `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.`
|
||||
|
||||
describe('AES', () => {
|
||||
|
||||
describe('Default', () => {
|
||||
it('Short', () => {
|
||||
const e = AES.encrypt(dataShort, keyShort)
|
||||
const d = AES.decrypt(e, keyShort)
|
||||
a.strictEqual(dataShort, d)
|
||||
})
|
||||
it('Long', () => {
|
||||
const e = AES.encrypt(dataLong, keyLong)
|
||||
const d = AES.decrypt(e, keyLong)
|
||||
a.strictEqual(dataLong, d)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GCM', () => {
|
||||
it('128', () => {
|
||||
const e = AES.encrypt(dataShort, keyShort, AES.Ciphers.AES_128_GCM)
|
||||
const d = AES.decrypt(e, keyShort)
|
||||
a.strictEqual(dataShort, d)
|
||||
})
|
||||
it('192', () => {
|
||||
const e = AES.encrypt(dataShort, keyShort, AES.Ciphers.AES_192_GCM)
|
||||
const d = AES.decrypt(e, keyShort)
|
||||
a.strictEqual(dataShort, d)
|
||||
})
|
||||
it('256', () => {
|
||||
const e = AES.encrypt(dataShort, keyShort, AES.Ciphers.AES_256_GCM)
|
||||
const d = AES.decrypt(e, keyShort)
|
||||
a.strictEqual(dataShort, d)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
60
tsconfig.json
Normal file
60
tsconfig.json
Normal file
@ -0,0 +1,60 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
/* Basic Options */
|
||||
"target": "esnext", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./dist/index.js", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./lib", /* Redirect output structure to the directory. */
|
||||
"rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
"removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
"strictNullChecks": true, /* Enable strict null checks. */
|
||||
"strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
"strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
"strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
"noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user