221 lines
5.8 KiB
TypeScript
221 lines
5.8 KiB
TypeScript
|
|
import { promises as fs } from "fs";
|
||
|
|
|
||
|
|
import { CryptContainer } from "./CryptContainer";
|
||
|
|
import * as crypto from "crypto";
|
||
|
|
import * as path from "path";
|
||
|
|
import * as bcrypt from "bcrypt";
|
||
|
|
|
||
|
|
import { CryptSettings } from "./CryptSettings";
|
||
|
|
import { Files } from "../files/Files";
|
||
|
|
import { RJLog } from "../log/RJLog";
|
||
|
|
|
||
|
|
|
||
|
|
export class CryptIO
|
||
|
|
{
|
||
|
|
static readonly encryptionSuffix = ".crypt";
|
||
|
|
static readonly debugOutput = true;
|
||
|
|
static readonly debugOutputSuffix = ".crypt.json";
|
||
|
|
|
||
|
|
private static _cryptSettings:CryptSettings;
|
||
|
|
|
||
|
|
static set cryptSettings( cryptSettings:CryptSettings )
|
||
|
|
{
|
||
|
|
this._cryptSettings = cryptSettings;
|
||
|
|
}
|
||
|
|
|
||
|
|
static createUUID()
|
||
|
|
{
|
||
|
|
return crypto.randomUUID();
|
||
|
|
}
|
||
|
|
|
||
|
|
static isDebugFilePath( filePath:string ):boolean
|
||
|
|
{
|
||
|
|
return filePath.endsWith( this.debugOutputSuffix );
|
||
|
|
}
|
||
|
|
|
||
|
|
static async hash( data:string ):Promise<string>
|
||
|
|
{
|
||
|
|
let hash = await bcrypt.hash( data, 10 );
|
||
|
|
return Promise.resolve( hash );
|
||
|
|
}
|
||
|
|
|
||
|
|
static async verifyHash( data:string, hashed:string ):Promise<boolean>
|
||
|
|
{
|
||
|
|
let isVerified = await bcrypt.compare( data, hashed );
|
||
|
|
return Promise.resolve( isVerified );
|
||
|
|
}
|
||
|
|
|
||
|
|
static async encrypt( data:string, settings?:CryptSettings ):Promise<string>
|
||
|
|
{
|
||
|
|
settings = settings || CryptIO._cryptSettings;
|
||
|
|
|
||
|
|
const algorithm = 'aes-192-cbc';
|
||
|
|
|
||
|
|
const key = crypto.scryptSync( settings.publicKey, 'salt', 24 );
|
||
|
|
|
||
|
|
const cipher = crypto.createCipheriv( algorithm, key, settings.iv );
|
||
|
|
|
||
|
|
let encrypted = cipher.update( data, 'utf8', 'hex');
|
||
|
|
encrypted += cipher.final('hex');
|
||
|
|
|
||
|
|
return Promise.resolve( encrypted );
|
||
|
|
}
|
||
|
|
|
||
|
|
static async decrypt( data:string, settings?:CryptSettings )
|
||
|
|
{
|
||
|
|
settings = settings || CryptIO._cryptSettings;
|
||
|
|
|
||
|
|
const algorithm = 'aes-192-cbc';
|
||
|
|
|
||
|
|
const key = crypto.scryptSync( settings.publicKey, 'salt', 24 );
|
||
|
|
|
||
|
|
const decipher = crypto.createDecipheriv( algorithm, key, settings.iv );
|
||
|
|
|
||
|
|
let decrypted = decipher.update( data, 'hex', 'utf8' );
|
||
|
|
|
||
|
|
decrypted += decipher.final('utf8');
|
||
|
|
|
||
|
|
return decrypted;
|
||
|
|
}
|
||
|
|
|
||
|
|
static async createWithRandomUUID<T>( parentPath:string, data:any, onUUID?:( uuid:string, data:T ) => T ):Promise<string>
|
||
|
|
{
|
||
|
|
let uuid = CryptIO.createUUID();
|
||
|
|
let fullPath = path.join( parentPath, uuid + this.encryptionSuffix );
|
||
|
|
|
||
|
|
let pathExists = await Files.exists( fullPath );
|
||
|
|
|
||
|
|
while ( pathExists )
|
||
|
|
{
|
||
|
|
uuid = CryptIO.createUUID();
|
||
|
|
fullPath = path.join( parentPath, uuid + this.encryptionSuffix );
|
||
|
|
pathExists = await Files.exists( fullPath );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( onUUID )
|
||
|
|
{
|
||
|
|
data = onUUID( uuid, data );
|
||
|
|
}
|
||
|
|
|
||
|
|
await CryptIO.saveJSON( fullPath, data );
|
||
|
|
|
||
|
|
|
||
|
|
|
||
|
|
return Promise.resolve( uuid );
|
||
|
|
}
|
||
|
|
|
||
|
|
static async loadWithRandomUUID<T>( parentPath:string, uuid:string ):Promise<T>
|
||
|
|
{
|
||
|
|
let fullPath = path.join( parentPath, uuid + this.encryptionSuffix );
|
||
|
|
|
||
|
|
let fileExsits = await Files.exists( fullPath );
|
||
|
|
|
||
|
|
if ( ! fileExsits )
|
||
|
|
{
|
||
|
|
return Promise.resolve( null );
|
||
|
|
}
|
||
|
|
|
||
|
|
let data = await this.loadJSON<T>( fullPath );
|
||
|
|
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
static async deleteWithRandomUUID( parentPath:string, uuid:string ):Promise<boolean>
|
||
|
|
{
|
||
|
|
let fullPath = path.join( parentPath, uuid + this.encryptionSuffix );
|
||
|
|
|
||
|
|
let exists = await Files.exists( fullPath );
|
||
|
|
|
||
|
|
if ( ! exists )
|
||
|
|
{
|
||
|
|
return Promise.resolve( false );
|
||
|
|
}
|
||
|
|
|
||
|
|
await Files.deleteFile( fullPath );
|
||
|
|
|
||
|
|
return Promise.resolve( true );
|
||
|
|
}
|
||
|
|
|
||
|
|
static async updateWithRandomUUID( parentPath:string, uuid:string, data:any ):Promise<void>
|
||
|
|
{
|
||
|
|
let fullPath = path.join( parentPath, uuid + this.encryptionSuffix );
|
||
|
|
await this.saveJSON( fullPath, data );
|
||
|
|
|
||
|
|
return Promise.resolve();
|
||
|
|
}
|
||
|
|
|
||
|
|
static async saveJSON( path:string, data:any ):Promise<void>
|
||
|
|
{
|
||
|
|
let jsonString = JSON.stringify( data );
|
||
|
|
|
||
|
|
RJLog.log( "Saving ", data, ">>", jsonString, );
|
||
|
|
|
||
|
|
let container:CryptContainer =
|
||
|
|
{
|
||
|
|
key:"",
|
||
|
|
data: await this.encrypt( jsonString )
|
||
|
|
};
|
||
|
|
|
||
|
|
let containerString = await this.encrypt( JSON.stringify( container ) );
|
||
|
|
|
||
|
|
await fs.writeFile( path, containerString );
|
||
|
|
|
||
|
|
if ( this.debugOutput )
|
||
|
|
{
|
||
|
|
await fs.writeFile( path + this.debugOutputSuffix, jsonString );
|
||
|
|
}
|
||
|
|
|
||
|
|
return Promise.resolve();
|
||
|
|
}
|
||
|
|
|
||
|
|
static async saveUTF8( path:string, jsonString:string ):Promise<void>
|
||
|
|
{
|
||
|
|
RJLog.log( "Saving ", jsonString, );
|
||
|
|
|
||
|
|
let container:CryptContainer =
|
||
|
|
{
|
||
|
|
key:"",
|
||
|
|
data: await this.encrypt( jsonString )
|
||
|
|
};
|
||
|
|
|
||
|
|
let containerString = await this.encrypt( JSON.stringify( container ) );
|
||
|
|
|
||
|
|
await fs.writeFile( path, containerString );
|
||
|
|
|
||
|
|
if ( this.debugOutput )
|
||
|
|
{
|
||
|
|
await fs.writeFile( path + this.debugOutputSuffix, jsonString );
|
||
|
|
}
|
||
|
|
|
||
|
|
return Promise.resolve();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static async loadJSON<T>( path:string ):Promise<T>
|
||
|
|
{
|
||
|
|
let encryptedContainerString = await fs.readFile( path );
|
||
|
|
let containerString = await this.decrypt( encryptedContainerString.toString() );
|
||
|
|
|
||
|
|
let container = JSON.parse( containerString ) as CryptContainer;
|
||
|
|
let encryptedData = container.data;
|
||
|
|
let jsonString = await this.decrypt( encryptedData );
|
||
|
|
|
||
|
|
let json = JSON.parse( jsonString ) as T;
|
||
|
|
|
||
|
|
return Promise.resolve( json );
|
||
|
|
}
|
||
|
|
|
||
|
|
static async loadUTF8( path:string ):Promise<string>
|
||
|
|
{
|
||
|
|
let encryptedContainerString = await fs.readFile( path );
|
||
|
|
let containerString = await this.decrypt( encryptedContainerString.toString() );
|
||
|
|
|
||
|
|
let container = JSON.parse( containerString ) as CryptContainer;
|
||
|
|
let encryptedData = container.data;
|
||
|
|
let jsonString = await this.decrypt( encryptedData );
|
||
|
|
|
||
|
|
return Promise.resolve( jsonString );
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
}
|