JavaScriptmediumJavaScript

Data Masking/Obfuscation

Mask sensitive data for logging or display

01

The problem

Need to mask sensitive information like emails, phone numbers, and credit cards

02

The solution

JavaScript
class DataMasker {
  constructor(options = {}) {
    this.options = {
      maskChar: '*',
      visibleChars: {
        email: 3,
        phone: 4,
        ssn: 4,
        creditCard: 4
      },
      preserveLength: true,
      ...options
    };
  }
  
  maskEmail(email, visibleChars = this.options.visibleChars.email) {
    if (!this.isValidEmail(email)) return email;
    
    const [local, domain] = email.split('@');
    const maskedLocal = this.maskString(local, visibleChars);
    
    return `${maskedLocal}@${domain}`;
  }
  
  maskPhone(phone, visibleChars = this.options.visibleChars.phone) {
    const digits = phone.replace(/\D/g, '');
    
    if (digits.length < 10) return phone;
    
    const lastDigits = digits.slice(-visibleChars);
    const maskedDigits = this.maskString(
      digits.slice(0, -visibleChars),
      0,
      this.options.preserveLength
    );
    
    return `${maskedDigits}${lastDigits}`;
  }
  
  maskSSN(ssn, visibleChars = this.options.visibleChars.ssn) {
    const digits = ssn.replace(/\D/g, '');
    
    if (digits.length !== 9) return ssn;
    
    const lastDigits = digits.slice(-visibleChars);
    const maskedDigits = this.maskString(
      digits.slice(0, -visibleChars),
      0,
      this.options.preserveLength
    );
    
    return `${maskedDigits}-${lastDigits}`;
  }
  
  maskCreditCard(cardNumber, visibleChars = this.options.visibleChars.creditCard) {
    const digits = cardNumber.replace(/\D/g, '');
    
    if (digits.length < 13 || digits.length > 19) return cardNumber;
    
    const lastDigits = digits.slice(-visibleChars);
    const maskedDigits = this.maskString(
      digits.slice(0, -visibleChars),
      0,
      this.options.preserveLength
    );
    
    // Format with spaces like XXXX XXXX XXXX 1234
    const masked = `${maskedDigits}${lastDigits}`;
    return masked.replace(/(.{4})/g, '$1 ').trim();
  }
  
  maskName(name, visibleChars = 2) {
    const parts = name.trim().split(/\s+/);
    
    return parts.map(part => {
      if (part.length <= visibleChars) {
        return this.maskString(part, 0);
      }
      return this.maskString(part, visibleChars);
    }).join(' ');
  }
  
  maskString(str, visibleChars = 0, preserveLength = this.options.preserveLength) {
    if (!str || str.length === 0) return str;
    
    if (visibleChars >= str.length) {
      return str;
    }
    
    const visiblePart = str.slice(0, visibleChars);
    const maskedLength = preserveLength ? str.length - visibleChars : 4;
    const maskedPart = this.options.maskChar.repeat(maskedLength);
    
    return `${visiblePart}${maskedPart}`;
  }
  
  maskObject(obj, maskRules = {}) {
    const masked = { ...obj };
    
    for (const [key, rule] of Object.entries(maskRules)) {
      if (masked[key] !== undefined && masked[key] !== null) {
        if (typeof rule === 'string') {
          // Simple field type
          masked[key] = this.maskByType(masked[key], rule);
        } else if (typeof rule === 'function') {
          // Custom mask function
          masked[key] = rule(masked[key]);
        } else if (typeof rule === 'object') {
          // Nested object with rules
          if (typeof masked[key] === 'object' && !Array.isArray(masked[key])) {
            masked[key] = this.maskObject(masked[key], rule);
          }
        }
      }
    }
    
    return masked;
  }
  
  maskByType(value, type) {
    switch (type.toLowerCase()) {
      case 'email':
        return this.maskEmail(String(value));
      case 'phone':
        return this.maskPhone(String(value));
      case 'ssn':
        return this.maskSSN(String(value));
      case 'creditcard':
        return this.maskCreditCard(String(value));
      case 'name':
        return this.maskName(String(value));
      default:
        return this.maskString(String(value));
    }
  }
  
  isValidEmail(email) {
    const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return regex.test(email);
  }
  
  setMaskChar(char) {
    this.options.maskChar = char;
    return this;
  }
  
  setVisibleChars(config) {
    this.options.visibleChars = { ...this.options.visibleChars, ...config };
    return this;
  }
}

03

Put it to work

Example
const masker = new DataMasker({
  maskChar: '*',
  visibleChars: {
    email: 3,
    phone: 4,
    ssn: 4,
    creditCard: 4
  }
});

// Mask individual fields
console.log(masker.maskEmail('[email protected]')); // "joh*****@example.com"
console.log(masker.maskPhone('+1 (555) 123-4567')); // "*********4567"
console.log(masker.maskSSN('123-45-6789')); // "*****6789"
console.log(masker.maskCreditCard('4111 1111 1111 1111')); // "**** **** **** 1111"
console.log(masker.maskName('John Doe')); // "Jo** Do*"

// Mask entire object
const userData = {
  id: 123,
  email: '[email protected]',
  phone: '555-123-4567',
  ssn: '123-45-6789',
  creditCard: '4111111111111111',
  address: {
    street: '123 Main St',
    city: 'Anytown'
  }
};

const maskRules = {
  email: 'email',
  phone: 'phone',
  ssn: 'ssn',
  creditCard: 'creditCard',
  address: {
    street: (value) => masker.maskString(value, 5)
  }
};

const maskedData = masker.maskObject(userData, maskRules);
console.log(maskedData);
// {
//   id: 123,
//   email: 'joh*****@example.com',
//   phone: '*******4567',
//   ssn: '*****6789',
//   creditCard: '**** **** **** 1111',
//   address: { street: '123 Ma*****', city: 'Anytown' }
// }

// Custom configuration
masker.setMaskChar('#');
masker.setVisibleChars({ email: 5, phone: 6 });

console.log(masker.maskEmail('[email protected]')); // "alice#####@example.com"