obcon-scada / app /include /Security.js
chanmin0723's picture
Initial obcon SCADA deploy
e4bf523
Raw
History Blame Contribute Delete
14.5 kB
'use strict'
/**
* Copyright (c) 2017~2024, OBCon Inc.
* All rights reserved.
*/
/**
* @file
* @copyright 2017~2024, OBCon Inc.
* @author gye hyun james kim [pnuskgh@gmail.com]
*/
const crypto = require('crypto');
class Security {
constructor() {
this._initialize();
}
_initialize() {
//--- Hash
this._hashTypes = [
'sha512', 'sha256', 'md5', 'sha1'
];
this._hashType = this._hashTypes[0];
this._digestTypes = [
'base64', 'hex', 'latin1'
];
this._digestType = this._digestTypes[0];
this._hash = crypto.createHash(this._hashType);
//--- RSA 암복호화의 Public key와 Private key
const publicKey = '-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxDjsWgMik7ZbfSe6frDTRmYT001/m3Yi+6UU4Vljev3/uNcf5YJO\neXFzL2bqzAqtA7mw7aJzvlyMVUXB/N2i3vz0bHc0SRJMfx1PI3C/TF3yVMRwyPrA\nTchP8g/SoXwymU4modVpe5hUVYUuJuS3zrT+YrFNoDiGRdzeZW+eAlPHyD/W9Rix\nSiJ8uEPdkkcGtw7PgmuCcNt3x5LAoIcjsBfdLBi0/ee7sgo3SZDWfyk7CrC6tCRA\nykMVeg9x9SmmBbRKeb5+38J3B0djkVudsOz5xWR+ETqhfmH9VDLQjNmcA6LdB1vA\nioW7AL/a4flW9gPd653JiFgaEhbP/sddzwIDAQAB\n-----END RSA PUBLIC KEY-----\n';
const privateKey = '-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-256-CBC,6EA33C8A9B1B435FB4D70734DF67774C\n\nzL40sX2LURVT8uszk5CFgIVhrIBXxHoWlflmQvGD0RK4A2w+c4pj5gCnCmGEQHed\nLC6k84OMw0STRs+T74frLby1KDaWh538yGs/aU7ZmD8zpa5X/gguORDJ+G8c+UcM\nlQ5dy2oUPG+QVcFNn428Tfr3V0EvhCdjtd+LQzNWlboQdXv+nSSUoAUKLMO9eJ6L\nokAoFeZlOVb777ELHaOVxJ2FuGcmGQwlJCJqbrTmWM2NIw4WzNtQ3FZ+hAd7mEgN\nzorYHtat2LdYCyvzjzdyMR6KGudcrn/DwiQI1ScfdMSqgpjAJWtsGYnIWX4mRpmR\nucjlo2YhWAocoa2mq7M1YDtALEAgAOeL0pYRY/Vr/qy0QF5pPcA16dSyeoFEppAh\nd41iHFofw+a+/NAApZPIGszuUAbZVziWnD+bkShsC10Vtnot42ctnDILOy5mgMZ5\nIXQMd1gN75nJrfvmffZGkiXsae4hjHj9KtM1ftTQE7mr5NMBdpQH+J1g+eBPGL6P\n0o0zVEHULSEIhAlkIT9Pcz6xG+5PX+wpKgWUjwmtMBIo3bgXbu5r9qQmei+w/Px3\nOC2TsJtjDpRrbVDmRGadmlKjWY+14VWsEGCesF5ouKCvMkjMaszSLVHfnayv8buZ\n5r3+usK4tA5a11USyhV1jQ2gap5D2hDyCmld3l0RhHfPLhIz3nO9i1wtw39lxQTe\nwWnYJrHgsWLglzrjp2rebqFcRcZxoI+4fuG2G1JjgQJaYZ5rLLe9iN2WIslKkscH\nMhqEzxuIX3cMcQ0hTK+hENDyvU9+DmPzY2E9f8ALWKScmcT5EAwkZK0jetZusLe+\nrbUtYnNuaoZ6e0blW+iwnWPqoCDHo3lGZnerZWD37l0cbTl7u+rjdLj9hdPqUgmc\nATPBvI9jxlM6neWRKUQR1GIBfafI+cK3SbNp6m0bigaz+F6snNez6Qk/9Ft7a+8h\ndNRiDaeSAU+NJd/X9JXtek3SfG/N2vYnqyY2KmJKjVI6wexu5DlY6kFOAjDvvccJ\nRR8DO6HjVKkXBtt3pW29DoFt8QjsM0fxGeSnUKXP5AJIYuran7jnmqIZYBgG0aOV\nCVjFWI8BKtkzdQcsRMC6fE/1zdZNJQjosWKYtlWXwbdx+H3lKtcZUOJre8YZs+4l\nZZ06IgNphTgSiJYuV20x7SGo4h/cgHFmPqd7vwezMj+I13SKujQW0yzwfs2TgdW4\nK3nNaWtqlfaMRAeK41k7qintmDycj/Vdilji+xgZXKGS99xEnAp5iiomSUkwlJ8h\nFO2YsHSGh6BdmnI4xeHnAZ9WW2JLsnQINhBofgOdhUxaTxWpB7YoJs/wqKX/uS8H\nrCJC/s8lu2Qe9EZmXpJqQsI6SlcZFxEK5p5knpdSR1KCSOBSqpdHViaHwb+q8TxB\nZB5c5FhSBz19MMqwLHiR94C56nnQd08VKftL3tBJszO9gWfzllz8CZQZm4PFA7E/\nuMC+fbCJsUD7nV09JgdEsaf/uddRi9obWMfZ5MH+94KKIpJNEQEM0IQwv50CIEwf\nuO39SowXxCYqvyKqjIWCt+bL84kKurX4aX8I7fo83FRNuASEFNNzopAxzmjERK5s\n-----END RSA PRIVATE KEY-----\n';
this._passphrase = 'pnuskgh@gmail.com';
this._publicKey = config.security.publicKey || publicKey;
this._privateKey = config.security.privateKey || privateKey;
//--- 블록 암호화/복호화 키
//--- AES (Advanced Encryption Standard)
this._cipherTypes = [
'aes-256-cbc', 'aes-192-cbc', 'aes-128-cbc',
// 'aes-256-ccm', 'aes-192-ccm', 'aes-128-ccm', //--- 검증 필요
// 'aes-128-gcm', 'aes-192-gcm', 'aes-256-gcm','chacha20-poly1305' //--- 검증 필요
];
this._cipherType = this._cipherTypes[0];
this._IV_LENGTH = 16; //--- IV (Initialize Vector)
this._encryptKey = this.decrypt(config.security.encryptKey || this._initEncryptKey());
}
get encryptKey() {
return this._encryptKey;
}
set encryptKey(theValue) {
this._encryptKey = theValue;
}
get publicKey() {
return this._publicKey;
}
set publicKey(theValue) {
this._publicKey = theValue;
}
_initEncryptKey(name = null) {
const hash = crypto.createHash('sha512');
const hostname = config.http.baseUrl.replace(/\//g, '').replace(/http:/, '').replace(/https:/, '').split(':')[0];
hash.update(name || `${config.service.name}:${hostname}`);
return hash.digest('hex').toLowerCase().substring(0, 32);
}
hash(plainText) {
this._hash.update(plainText);
return this._hash.digest(this._digestType).toLowerCase();
}
hashWithSalt(plainText, hash=null, callback) {
let salt = null;
if (hash == null) {
salt = crypto.randomBytes(this._IV_LENGTH).toString(this._digestType);
} else {
salt = hash.split(':')[0];
}
crypto.pbkdf2(plainText, salt, 1000, 64, this._hashType, function(err, key) {
if (err) {
callback(null);
} else {
callback(`${salt}:${key.toString(this._digestType)}`);
}
}.bind(this));
}
// this._encryptKey = 'f36ff20a60'; //--- 10자, 40 bits 키
// this._encryptKey = 'af0af00a9ea2c23c403485994ce78b4e'; //--- 32자, 128 bits 키
// this._encryptKey = '14889b604f7d72880e71734373ef11639f0ac1b3cb40955fade3a8848aae8407'; //--- 64자, 256 bits 키
_getEncryptKey() {
let key = null;
try {
switch (this._cipherType) {
case 'aes-128-cbc':
case 'aes-128-ccm':
case 'aes-128-gcm':
key = crypto.scryptSync(this._encryptKey, 'GfG', 16);
break;
case 'aes-192-cbc':
case 'aes-192-ccm':
case 'aes-192-gcm':
key = crypto.scryptSync(this._encryptKey, 'GfG', 24);
break;
case 'aes-256-cbc':
case 'aes-256-ccm':
case 'aes-256-gcm':
default:
if (this._encryptKey.length == 32) {
key = Buffer.from(this._encryptKey);
} else {
//--- this._encryptKey가 32 bytes가 아니면 오류 발생
key = crypto.scryptSync(this._encryptKey, 'GfG', 32);
}
break;
}
} catch (e) {
utils.obj.loggerError(e);
}
return key;
}
//--- encrypt:{IV(Initialize Vector)}:{encryptText}
//--- rsa:{encryptText}
encrypt(plainText, key=null, isPublic=true) {
return (key == null) ? this._encrypt(plainText):this.encryptRSA(plainText, key, isPublic);
}
decrypt(encryptedText, key=this._privateKey, isPublic=false) {
let planText = encryptedText;
if (typeof(encryptedText) == 'string') { //--- string, number
if (encryptedText.startsWith('encrypt:')) {
planText = this._decrypt(encryptedText);
}
if (encryptedText.startsWith('rsa:')) {
planText = this.decryptRSA(encryptedText, key, isPublic);
}
if ((planText.startsWith('encrypt:')) || (planText.startsWith('rsa:'))) {
planText = this.decrypt(planText, key, isPublic);
}
}
return planText;
}
_encrypt(plainText) {
try {
const key = this._getEncryptKey();
let iv = null;
let cipher = null;
if (this._cipherType.endsWith('-cbc')) {
iv = crypto.randomBytes(this._IV_LENGTH);
cipher = crypto.createCipheriv(this._cipherType, key, iv);
} else if (this._cipherType.endsWith('-ccm')) {
iv = crypto.randomBytes(12);
cipher = crypto.createCipheriv(this._cipherType, key, iv, { authTagLength: 16 });
cipher.setAAD(Buffer.from('0123456789', 'hex'), {
plaintextLength: Buffer.byteLength(plainText)
});
} else {
iv = crypto.randomBytes(this._IV_LENGTH);
cipher = crypto.createCipheriv(this._cipherType, key, iv);
}
const encrypted = cipher.update(plainText);
//--- 32 bytes : 32 bytes * n
return `encrypt:${iv.toString(this._digestType)}:${Buffer.concat([encrypted, cipher.final()]).toString(this._digestType)}`;
} catch (e) {
utils.obj.loggerError(e);
return plainText;
}
}
_decrypt(encryptedText) {
try {
if (encryptedText.startsWith('encrypt:') == false) {
return encryptedText;
}
encryptedText = encryptedText.substring(8);
const key = this._getEncryptKey();
const textParts = encryptedText.split(':');
const iv = Buffer.from(textParts.shift(), this._digestType);
const encrypted = Buffer.from(textParts.join(':'), this._digestType);
let decipher = null;
if (this._cipherType.endsWith('-cbc')) {
decipher = crypto.createDecipheriv(this._cipherType, key, iv);
} else if (this._cipherType.endsWith('-ccm')) {
decipher = crypto.createDecipheriv(this._cipherType, key, iv, { authTagLength: 16 });
decipher.setAAD(Buffer.from('0123456789', 'hex'), {
plaintextLength: encrypted.length
});
} else {
decipher = crypto.createDecipheriv(this._cipherType, key, iv);
}
const decrypted = decipher.update(encrypted);
return Buffer.concat([decrypted, decipher.final()]).toString();
} catch (e) {
utils.obj.loggerError(e);
return encryptedText;
}
}
//--- publicKey, privateKey 반환
getKeyPair() {
// return crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
return crypto.generateKeyPairSync(
'rsa', //--- rsa, dsa, ec, ed25519, ed448, x25519, x448, dh
{
modulusLength: 2048,
publicKeyEncoding: {
type: 'pkcs1', //--- pkcs1, spki
format: 'pem' //--- pem, der
},
privateKeyEncoding: {
type: 'pkcs1', //--- sec1, pkcs8
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: this._passphrase
}
},
// (err, publicKey, privateKey) => { }
);
}
encryptRSA(plainText, key=this._publicKey, isPublic=true) {
try {
let cipher = null;
if (isPublic) {
cipher = crypto.publicEncrypt(
{
key: key,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: this._hashType //--- sha256, sha512
},
Buffer.from(plainText)
);
} else {
cipher = crypto.privateEncrypt(
{
key: key,
cipher: 'aes-256-cbc',
passphrase: this._passphrase
},
Buffer.from(plainText)
);
}
return `rsa:${cipher.toString(this._digestType)}`;
} catch (e) {
utils.obj.loggerError(e);
return plainText;
}
}
decryptRSA(encryptedText, key=this._privateKey, isPublic=false) {
try {
if (encryptedText.startsWith('rsa:') == false) {
return encryptedText;
}
encryptedText = encryptedText.substring(4);
let decipher = null;
if (isPublic) {
decipher = crypto.publicDecrypt(
{
key: key,
},
Buffer.from(encryptedText, this._digestType)
);
} else {
decipher = crypto.privateDecrypt(
{
key: key,
cipher: 'aes-256-cbc',
passphrase: this._passphrase,
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
oaepHash: this._hashType //--- sha256, sha512
},
Buffer.from(encryptedText, this._digestType)
);
}
return decipher.toString();
} catch (e) {
utils.obj.loggerError(e);
return encryptedText;
}
}
//--- 서명 반환
signature(text, privateKey=this._privateKey) {
try {
const signer = crypto.sign(
this._hashType,
Buffer.from(text),
{
key: privateKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
cipher: 'aes-256-cbc',
passphrase: this._passphrase
}
);
return signer.toString(this._digestType);
} catch (e) {
utils.obj.loggerError(e);
return text;
}
}
//--- 서명 검증
verify(text, sign, publicKey=this._publicKey) {
try {
return crypto.verify(
this._hashType,
Buffer.from(text),
{
key: publicKey,
padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
},
Buffer.from(sign, this._digestType)
);
} catch (e) {
utils.obj.loggerError(e);
return false;
}
}
}
module.exports = Security;