Skip to content
This repository has been archived by the owner on Sep 16, 2024. It is now read-only.

Commit

Permalink
feat: encrypt pii data (#203)
Browse files Browse the repository at this point in the history
Co-authored-by: jatin <[email protected]>
  • Loading branch information
ashutoshdhande and jatinsandilya authored Aug 23, 2023
1 parent 2e99f97 commit ba2e854
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 2 deletions.
1 change: 1 addition & 0 deletions packages/backend/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const config = {
SHORTLOOP_AUTH_KEY: process.env.SHORTLOOP_AUTH_KEY!,
SVIX_ENDPOINT_SECRET: process.env.SVIX_ENDPOINT_SECRET!,
svix: new Svix(process.env.SVIX_AUTH_TOKEN!),
AES_ENCRYPTION_SECRET: process.env.AES_ENCRYPTION_SECRET!,
};

export default config;
48 changes: 48 additions & 0 deletions packages/backend/helpers/gcmUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import crypto from 'crypto';

const CIPHER_ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const TAG_LENGTH = 16;
const SALT_LENGTH = 64;
const ITERATIONS = 10000;

const tagPosition = SALT_LENGTH + IV_LENGTH;
const encryptedPosition = tagPosition + TAG_LENGTH;

const getKey = (salt: Buffer, secret: string) => {
return crypto.pbkdf2Sync(secret, salt, ITERATIONS, 32, 'sha256');
};

const gcm = {
encrypt: (input: string, secret: string) => {
const iv = crypto.randomBytes(IV_LENGTH);
const salt = crypto.randomBytes(SALT_LENGTH);

const AES_KEY = getKey(salt, secret);

const cipher = crypto.createCipheriv(CIPHER_ALGORITHM, AES_KEY, iv);
const encrypted = Buffer.concat([cipher.update(String(input), 'utf8'), cipher.final()]);

const tag = cipher.getAuthTag();

return Buffer.concat([salt, iv, tag, encrypted]).toString('hex');
},

decrypt: (input: string, secret: string) => {
const inputValue = Buffer.from(String(input), 'hex');
const salt = inputValue.subarray(0, SALT_LENGTH);
const iv = inputValue.subarray(SALT_LENGTH, tagPosition);
const tag = inputValue.subarray(tagPosition, encryptedPosition);
const encrypted = inputValue.subarray(encryptedPosition);

const key = getKey(salt, secret);

const decipher = crypto.createDecipheriv(CIPHER_ALGORITHM, key, iv);

decipher.setAuthTag(tag);

return decipher.update(encrypted) + decipher.final('utf8');
},
};

export default gcm;
66 changes: 66 additions & 0 deletions packages/backend/prisma/client.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,72 @@
import { PrismaClient, Prisma } from '@prisma/client';
import config from '../config';
import gcm from '../helpers/gcmUtil';

const prisma = new PrismaClient();

// Prisma middleware to encrypt and decrypt data on fly
prisma.$use(async (params, next) => {
if (params.model !== 'connections') {
return next(params);
}

const isReadOperation = ['findFirst', 'findMany', 'delete'].includes(params.action);
const isWriteOperation = ['update', 'upsert'].includes(params.action);

if (isReadOperation) {
return handleReadOperation(params, next);
} else if (isWriteOperation) {
return handleWriteOperation(params, next);
}
return next(params);
});

async function handleReadOperation(
params: Prisma.MiddlewareParams,
next: (params: Prisma.MiddlewareParams) => Promise<any>
): Promise<any> {
try {
const connections = await next(params);
if (!connections) return connections;
if (Array.isArray(connections)) {
connections.forEach((connection) => {
connection.tp_customer_id = gcm.decrypt(connection.tp_customer_id, config.AES_ENCRYPTION_SECRET);
});
} else {
connections.tp_customer_id = gcm.decrypt(connections.tp_customer_id, config.AES_ENCRYPTION_SECRET);
}
return connections;
} catch (error: any) {
throw new Error(error);
}
}

async function handleWriteOperation(
params: Prisma.MiddlewareParams,
_: (params: Prisma.MiddlewareParams) => Promise<any>
): Promise<any> {
try {
if (Array.isArray(params.args?.data)) {
params.args?.data.forEach((connection: any) => {
connection.tp_customer_id = gcm.encrypt(connection.tp_customer_id, config.AES_ENCRYPTION_SECRET);
});
} else {
if (params.action === 'upsert') {
params.args.create.tp_customer_id = gcm.encrypt(
params.args.create.tp_customer_id,
config.AES_ENCRYPTION_SECRET
);
} else {
params.args.data.tp_customer_id = gcm.encrypt(
params.args.data.tp_customer_id,
config.AES_ENCRYPTION_SECRET
);
}
}
} catch (error: any) {
throw new Error(error);
}
}

export default prisma;
export { Prisma };
3 changes: 1 addition & 2 deletions packages/backend/services/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,7 @@ const connectionService = new ConnectionService({
const deleted: any = await prisma.connections.delete({
// TODO: Add environments to connections.
where: {
uniqueCustomerPerTenantPerThirdParty: {
tp_customer_id: connection.tp_customer_id,
uniqueThirdPartyPerTenant: {
t_id: connection.t_id,
tp_id: connection.tp_id,
},
Expand Down
49 changes: 49 additions & 0 deletions scripts/backend/encrypt_customerEmails.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const cryp = require('crypto');

const CIPHER_ALGORITHM = 'aes-256-gcm';
const IV_LENGTH = 16;
const TAG_LENGTH = 16;
const SALT_LENGTH = 64;
const ITERATIONS = 10000;

const tagPosition = SALT_LENGTH + IV_LENGTH;
const encryptedPosition = tagPosition + TAG_LENGTH;

const getKey = (salt, secret) => {
return cryp.pbkdf2Sync(secret, salt, ITERATIONS, 32, 'sha256');
};

const gcm = {
encrypt: (input, secret) => {
const iv = cryp.randomBytes(IV_LENGTH);
const salt = cryp.randomBytes(SALT_LENGTH);

const AES_KEY = getKey(salt, secret);

const cipher = cryp.createCipheriv(CIPHER_ALGORITHM, AES_KEY, iv);
const encrypted = Buffer.concat([cipher.update(String(input), 'utf8'), cipher.final()]);

const tag = cipher.getAuthTag();

return Buffer.concat([salt, iv, tag, encrypted]).toString('hex');
},

decrypt: (input, secret) => {
const inputValue = Buffer.from(String(input), 'hex');
const salt = inputValue.subarray(0, SALT_LENGTH);
const iv = inputValue.subarray(SALT_LENGTH, tagPosition);
const tag = inputValue.subarray(tagPosition, encryptedPosition);
const encrypted = inputValue.subarray(encryptedPosition);

const key = getKey(salt, secret);

const decipher = cryp.createDecipheriv(CIPHER_ALGORITHM, key, iv);

decipher.setAuthTag(tag);

return decipher.update(encrypted) + decipher.final('utf8');
},
};

// Usage
// console.log('[email protected] :', gcm.encrypt('[email protected]', process.env.AES_ENCRYPTION_SECRET));

1 comment on commit ba2e854

@vercel
Copy link

@vercel vercel bot commented on ba2e854 Aug 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

revert-client – ./

app.revert.dev
revert-client-git-main-revertdev.vercel.app
revert-client-revertdev.vercel.app

Please sign in to comment.