Evervault raises €21M to keep payment data encrypted from end to end
Back to Tutorials
techTutorialintermediate

Evervault raises €21M to keep payment data encrypted from end to end

March 5, 202636 views5 min read

Learn to implement end-to-end encryption for payment data using Node.js and cryptographic libraries, similar to what companies like Evervault are doing to protect sensitive transaction information.

Introduction

In today's digital payment landscape, protecting sensitive data from end to end is crucial. Evervault's approach to encryption demonstrates how modern startups are solving complex security challenges. This tutorial will teach you how to implement end-to-end encryption for payment data using JavaScript and Node.js, similar to what companies like Evervault are doing in the industry.

Prerequisites

  • Basic understanding of JavaScript and Node.js
  • Node.js installed (version 14 or higher)
  • npm package manager
  • Basic knowledge of cryptographic concepts
  • Text editor or IDE

Step-by-step instructions

1. Setting up the project

1.1 Create a new Node.js project

First, we'll initialize our project and install the necessary dependencies. The encryption techniques we'll implement are similar to those used by companies processing millions of transactions.

mkdir evervault-encryption-demo
 cd evervault-encryption-demo
 npm init -y

1.2 Install required packages

We need several cryptographic libraries to implement end-to-end encryption. The crypto module is built into Node.js, but we'll also use node-forge for additional security features.

npm install node-forge

2. Implementing basic encryption functions

2.1 Create the encryption module

Let's create a file called encryption.js that will contain our core encryption logic. This mimics how payment systems handle sensitive data encryption.

const crypto = require('crypto');
const forge = require('node-forge');

// Generate a secure random key for encryption
function generateEncryptionKey() {
  return crypto.randomBytes(32); // 256-bit key
}

// Encrypt payment data
function encryptPaymentData(data, key) {
  const iv = crypto.randomBytes(16); // 128-bit IV
  const cipher = crypto.createCipher('aes-256-cbc', key);
  
  let encrypted = cipher.update(data, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  
  // Return both encrypted data and IV for decryption
  return {
    encryptedData: encrypted,
    iv: iv.toString('hex')
  };
}

// Decrypt payment data
function decryptPaymentData(encryptedData, iv, key) {
  const decipher = crypto.createDecipher('aes-256-cbc', key);
  
  let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  
  return decrypted;
}

module.exports = {
  generateEncryptionKey,
  encryptPaymentData,
  decryptPaymentData
};

2.2 Create a payment processor module

Now let's create a payment processor that simulates how payment data flows through a system while maintaining encryption.

const { generateEncryptionKey, encryptPaymentData, decryptPaymentData } = require('./encryption');

// Simulate payment processor with encryption
class PaymentProcessor {
  constructor() {
    this.encryptionKey = generateEncryptionKey();
    this.paymentDataStore = new Map();
  }
  
  // Process a payment transaction
  processPayment(transactionId, paymentData) {
    console.log(`Processing payment ${transactionId}`);
    
    // Encrypt the payment data before storing
    const encrypted = encryptPaymentData(paymentData, this.encryptionKey);
    
    // Store encrypted data
    this.paymentDataStore.set(transactionId, {
      encryptedData: encrypted.encryptedData,
      iv: encrypted.iv,
      timestamp: new Date()
    });
    
    console.log('Payment data encrypted and stored securely');
    return transactionId;
  }
  
  // Retrieve and decrypt payment data
  retrievePayment(transactionId) {
    const storedData = this.paymentDataStore.get(transactionId);
    
    if (!storedData) {
      throw new Error('Payment data not found');
    }
    
    // Decrypt the data
    const decrypted = decryptPaymentData(
      storedData.encryptedData,
      storedData.iv,
      this.encryptionKey
    );
    
    return {
      transactionId,
      decryptedData: decrypted,
      timestamp: storedData.timestamp
    };
  }
}

module.exports = PaymentProcessor;

3. Testing the encryption implementation

3.1 Create a test file

Let's create a test file to verify our encryption works correctly. This demonstrates how payment systems ensure data remains secure even if the system is compromised.

const PaymentProcessor = require('./payment-processor');

// Initialize payment processor
const processor = new PaymentProcessor();

// Sample payment data
const paymentData = {
  cardNumber: '4242424242424242',
  expiryDate: '12/25',
  cvv: '123',
  amount: 100.00,
  currency: 'USD'
};

// Process a payment
const transactionId = processor.processPayment('TXN001', JSON.stringify(paymentData));
console.log('Transaction processed:', transactionId);

// Retrieve and decrypt payment data
try {
  const retrievedData = processor.retrievePayment('TXN001');
  console.log('Decrypted payment data:', retrievedData.decryptedData);
  
  // Verify data integrity
  const originalData = JSON.parse(retrievedData.decryptedData);
  console.log('Amount:', originalData.amount);
  console.log('Card number:', originalData.cardNumber);
} catch (error) {
  console.error('Error retrieving payment data:', error.message);
}

3.2 Run the test

Execute the test to see how our encryption system works in practice:

node test-encryption.js

4. Advanced encryption with key management

4.1 Implement key rotation

For production systems, key rotation is essential. Let's add a method to rotate encryption keys:

// Add to PaymentProcessor class
rotateKey() {
  this.encryptionKey = generateEncryptionKey();
  console.log('Encryption key rotated successfully');
}

4.2 Add data integrity checks

Implement message authentication codes (MACs) to ensure data hasn't been tampered with:

function generateMAC(data, key) {
  const hmac = crypto.createHmac('sha256', key);
  hmac.update(data);
  return hmac.digest('hex');
}

function verifyMAC(data, mac, key) {
  const expectedMAC = generateMAC(data, key);
  return crypto.timingSafeEqual(
    Buffer.from(expectedMAC),
    Buffer.from(mac)
  );
}

5. Production considerations

5.1 Secure key storage

In production, never hardcode encryption keys. Use environment variables or secure key management services:

// Example of secure key handling
const encryptionKey = process.env.ENCRYPTION_KEY;
if (!encryptionKey) {
  throw new Error('Encryption key must be set in environment variables');
}

5.2 Error handling and logging

Implement proper error handling for production use:

function secureProcessPayment(transactionId, paymentData) {
  try {
    // Validate input
    if (!transactionId || !paymentData) {
      throw new Error('Invalid transaction data');
    }
    
    // Process payment
    return this.processPayment(transactionId, paymentData);
  } catch (error) {
    console.error('Payment processing error:', error.message);
    throw error;
  }
}

Summary

This tutorial demonstrated how to implement end-to-end encryption for payment data using Node.js and standard cryptographic libraries. We created a payment processor that encrypts sensitive data at rest and ensures it remains secure even if the system is compromised. Key concepts covered include:

  • Generating secure encryption keys
  • Implementing AES-256 encryption with proper IV handling
  • Secure data storage and retrieval
  • Key rotation and data integrity verification
  • Production-ready error handling

Similar to Evervault's approach, this implementation ensures that payment data remains encrypted from the point of entry to storage, significantly reducing compliance costs and security risks. The techniques shown here are fundamental to modern payment systems that process millions of transactions while maintaining security standards.

Source: TNW Neural

Related Articles