How a job interview led me to create Obscura - A password generator with real entropy

Introduction

During a recent interview for a Security Software Engineer position, I was given a challenge: create a secure password generator that takes into account real entropy. The interviewer wanted to see how I would approach the problem, and I was excited to take on the challenge.

At first, I thought it would be a simple task. I had used password generators before (with Math.Random stuffs), and I assumed it would be easy to create one that was secure. But the conversation quickly steered away from superficial implementations and into deeper, often overlooked topic: entropy—and how most so-called generators don't handle it properly.

The challenge

The interviewer insisted on entropy being measurable, adjustable, and predictable based on user choice. He wasn't looking for a simple tool that just throws random characters together; he wanted a password generator that would :

  • quantify the information entropy of the generated password,
  • adapt based on character pool constraints (like avoiding duplicates or special characters),
  • prevent common patterns and sequences (e.g., "123" or "qwerty" or "abc"),
  • and still generate passwords that are straightforward to use and robust against brute-force attacks.

That got me thinking about how most password generators out there are just glorified random characters generators.

My thought process: understanding entropy

I started by asking him about the definition of entropy in the context of password generation. He explained that entropy is a measure of uncertainty or randomness in a system. In the case of passwords, it refers to the unpredictability of the password itself. The more unpredictable a password is, the higher its entropy, and the more secure it is against brute-force attacks. (I said to my-self: come on man, you were supposed to make understand the concept not complicate it 🙁 ? because I don't know how to measure it, and I don't know how to calculate it).

After a few minutes of discussion, I was given the entropy formula which was quite simple but powerful:

E = log2(P^L)

Where:

  • E is the entropy in bits
  • P is the size of the character pool (e.g., 26 for lowercase letters, 10 for digits, etc.)
  • L is the length of the password

And here is the example of the entropy calculation he gave me: For a password using 26 chars of lowercase letters and a length of 8:

E = log2(26^8) = 8 * log2(26) ≈ 37.6 bits

Strong passwords should have at least 80 bits of entropy, so I needed to ensure that my generator increases the entropy by using :

  1. A larger character pool (e.g., uppercase and lowercase letters, digits, special characters)
  2. A longer password length
  3. True randomness in character selection (not just pseudo-randomness)

After all these details, I understand two things:

  • The larger the character pool and the longer the password, the stronger the password.
  • Entropy is measured in bits, and more bits means more possible combinations.

But if we exclude similar characters, enforce uniqueness, and start with specific characters, the pool shrinks—reducing entropy. So we need to calculate entropy dynamically based on current user choice

The solution: Build a real password generator

I wrote the first version of the password generator in TypeScript, focused purely on logic. It supports options like:

  • include/exclude uppercase, lowercase, digits, special characters
  • exclude similar characters (e.g., "l" and "1", "O" and "0")
  • avoid duplicates and sequential patterns
  • generate multiple passwords at once
  • and most importantly: realtime entropy calculation

Design approach

My implementation follows a modular approach centered around the core generatePassword function. I defined a clear PasswordOptions interface to make the code more readable and type-safe.

export interface PasswordOptions {
  length: number
  includeLowercase: boolean
  includeUppercase: boolean
  includeDigits: boolean
  includeSymbols: boolean
  excludeSimilarCharacters: boolean
  noDuplicateCharacters: boolean
  noSequentialCharacters: boolean
  beginWithLetter: boolean
  quantity: number
}

Character Pool generation

The foundation of my code is the character pool selection. I defined constant of character sets and wrote a helper function to build customized pool based on user preferences:

function getCharacterPool(options: PasswordOptions): string {
  let pool = ''
  if (options.includeLowercase) pool += LOWERCASE
  if (options.includeUppercase) pool += UPPERCASE
  if (options.includeDigits) pool += DIGITS
  if (options.includeSymbols) pool += SYMBOLS
  if (options.excludeSimilarCharacters) {
    pool = pool
      .split('')
      .filter((c) => !SIMILAR.includes(c))
      .join('')
  }
  return pool
}

This approach gives users fine-grained control over the character set, which is directly impacting the entropy calculation.

Security considerations

I implemented several security features to ensure strong password generation:

  1. Cryptographically secure randomness: instead of using Math.random(), I used the Web Crypto API crypto.getRandomValues() for true randomness:
        function getRandomChar(pool: string): string {
             const randomIndex = crypto.getRandomValues(new Uint32Array(1))[0] % pool.length
             return pool.charAt(randomIndex)
         }
    
  2. Sequential character detection: to prevent weak pattens like "abc" or "123", I added a function to check for sequential characters:
     function hasSequentialCharacters(password: string): boolean {
       for (let i = 0; i < password.length - 2; i++) {
         const a = password.charCodeAt(i)
         const b = password.charCodeAt(i + 1)
         const c = password.charCodeAt(i + 2)
         if ((b === a + 1 && c === b + 1) || (b === a - 1 && c === b - 1)) {
           return true
         }
       }
       return false
     }
    
  3. Input validation: The code prevents impossible combinations, like requiring unique characters
    in a password longer than the available character pool:

Password generation logic

The core algorithm uses a retry mechanism to ensure all constraints are satisfied:

  1. generate teh first character (optionally a letter)
  2. build the rest of the password character by character
  3. validate against all constraints (uniqueness, sequential patterns, etc.)
  4. if any constraint fails, retry the entire password generation

This approach ensures that every generated password meets all security requirements.

Final output

The generatePassword function ties everything together, enforcing sensible limits (8–64 characters) and generating the requested quantity of passwords:

export function generatePasswords(options: PasswordOptions): string[] {
  const safeLength = Math.max(8, Math.min(options.length, 64))
  const passwords: string[] = []

  for (let i = 0; i < options.quantity; i++) {
    passwords.push(generatePassword({ ...options, length: safeLength }))
  }
  return passwords
}

This implementation balances security, usability, and performance, resulting in a robust password generator suitable for real-world applications.

Why this matter

I learned a lot from this interview challenge, and I realized that most password generators out there are just glorified random character generators. They don't take into account the real entropy. Good password hygiene is crucial—and we've all reused passwords at some point. Tools like Obscura give users confidence and control without compromising on privacy.

Here are few tips for better password security:

  1. always use unique passwords for each account
  2. prefer longer passwords (16+ characters)
  3. avoid predictable pattens or using personal information (like birthdays or names)
  4. use a password manager (Obscura is great for generating strong password to store there)
  5. never, and ever share your passwords with anyone

My journey experience

Unfortunately, my journey with the company didn't go further than the technical interview step. According to their feedback, they're specifically looking for someone with experience in software engineering and formal security certifications like CISSP, CLSSP, COMPTIA Security+, etc.

My background is primarily in software engineering in distributed systems, with a focus on Kubernetes and DevSecOps practices. While I have experience with security aspects like SAST/DAST code analysis, dependency vulnerability scanning, container security, IaC security reviews, and IAM/secrets management, they were looking for someone with a more specialized security background.

Despite not getting the position, I thoroughly enjoyed the interview process and learned a lot from the challenge. I decided to take the opportunity to build something useful out of it, and that's how I ended up creating Obscura.

Try it, Fork it, Share it or Contribute

You can try obscura at obscura.denisakp.me and check the code on GitHub. I would love to hear your feedback, suggestions, or contributions. This project is open-source, and I welcome any improvements or new features you might want to add.