Devot Logo
Devot Logo
Arrow leftBack to blogs

A Developer’s Guide to Migrating Password Encryption Safely

Jerko Č.12 min readJan 7, 2025Technology
Jerko Č.12 min read
Contents:
What is password encryption?
Why do we encrypt passwords?
How to encrypt a password?
Encryption algorithms
Why is hashing so commonly used for password encryption
How do you validate a password that no one can read?
Let’s see some hashing examples
What is password salting?
Storing salts
Finally, how to migrate password encryption algorithm seamlessly

From time to time, there’s a need to strengthen system security. In certain cases, this involves improving the password encryption algorithm. So, what does that actually entail?

In our case, a very simple explanation of hardening a password encryption algorithm would be:

Making it more difficult for hackers to compromise user passwords by using stronger and more secure algorithms for storing or validating them.

Sounds straightforward, right? But things get complicated when it’s not just about upgrading a random feature but about updating the password encryption algorithm on a live system—one that’s already using password encryption with hundreds of thousands of active users. And to make it even trickier, the entire process needs to be seamless so that users don’t notice a thing.

So, what makes upgrading a password encryption algorithm so challenging? Let’s see together!

What is password encryption?

Password encryption is simply the encryption of a password. Just like encrypting a user’s first name means converting their first name into an encrypted format, password encryption is the same concept applied to passwords.

We all know what a password is, but not everyone is familiar with encryption. So, let’s explore what encryption actually means.

Encryption is the process of converting human-readable and understandable text into a list of random characters that are not readable or understandable by humans without losing original/initial information.

There are many definitions of encryption, but they all boil down to the same basic idea. In simple terms:

Encryption is the process of converting human-readable and understandable text into a string of random characters that is not readable or understandable without a key while preserving the original information.

By this definition, password encryption means converting a user’s password into a string of random characters that is unreadable to humans. At the same time, the password isn’t lost—it’s hidden within those random characters in a way that allows it to be retrieved or verified later.

password encryption

Why do we encrypt passwords?

The obvious reason is security. But why exactly? And more specifically, why encrypt passwords? Why not encrypt something like a user’s first name? Should that be encrypted too, or not? If the first name doesn’t need encryption, why does the password? What’s the difference?

Let’s first answer the question: why should passwords be encrypted but not something like a user’s first name?

The answer lies in the nature of the information. A password is a secret known only to the user, and it provides access or authorization to specific accounts, information, or actions. In contrast, a user’s first name is not secret—it’s generally public information.

Alright, that makes sense. But the next question is: why use encryption at all?

To understand this, let’s consider a few scenarios where passwords are stored in plain text:

  • Database breaches:
    If the database falls into the hands of hackers, they can simply read the stored emails and passwords and use them for malicious purposes, such as taking over accounts or performing unauthorized actions.

  • Internal misuse:
    Employees with access to the database could also read emails and passwords and misuse them for malicious purposes.

  • Security vulnerabilities:
    Bugs or other security flaws could allow unauthorized individuals to access the database. If passwords are stored in plain text, they can be easily used maliciously—especially since many users reuse the same email and password across multiple sites.

All of these scenarios—and many more—can be effectively prevented by storing passwords in an encrypted format instead of plain text. Encryption ensures that even if someone gains access to the database, the passwords remain protected and unreadable without the proper decryption process.

Now that we understand why password encryption is crucial, let’s explore how to encrypt passwords!

How to encrypt a password?

So, we know it’s possible to convert a user-readable password into a string of random characters that are not understandable to humans—and later retrieve the original password from it. That’s a powerful ability. But the real question is: how do we actually do that? How does encryption work?

This is where encryption algorithms come into play. We’re not the ones doing the encryption—encryption algorithms handle that for us.

If we want to encrypt something, we won’t do it manually. Instead, we use an encryption algorithm to perform the operation. The same principle applies to the reverse process: retrieving the original information from the encrypted data. This reverse operation is the opposite of encryption, and it’s called decryption.

Now, let’s dive a little deeper into encryption algorithms and how they work.

Encryption algorithms

What is an encryption algorithm?

An algorithm, in general, is defined as:

A set of step-by-step instructions to accomplish a task or solve a problem, often used in computer science.

Based on this, an encryption algorithm is a set of step-by-step instructions designed to encrypt data. Over time, as different people tackled the problem of encryption, various solutions (step-by-step instructions) emerged, leading to the creation of multiple encryption algorithms.

The role of encryption keys

One key characteristic of encryption algorithms is the encryption key. The concept is similar to how keys work with locks on doors: if you lock a door with a particular key, you need the same key to unlock it. Encryption keys function in much the same way.

In encryption, the key is a secret used by the algorithm to encrypt specific data. To decrypt the encrypted data, you must use the same key. This flexibility allows the same encryption algorithm to work securely for different users simply by using unique keys. Imagine a hotel where all the doors are identical, but each room requires a specific key to open. Without the correct key, you can’t access the room. Encryption works in much the same way.

However, not all encryption algorithms work identically. Some use the same key for both encryption and decryption, while others use two separate keys—one for encryption and another for decryption.

Types of encryption algorithms

Encryption algorithms are broadly divided into three distinct categories based on how they use keys:

1. Symmetric encryption algorithms

Symmetric encryption algorithms use the same key for both encryption and decryption.

2. Asymmetric encryption algorithms

Asymmetric encryption algorithms use two different keys:

  • Public key: Used for encryption and can be shared with anyone.

  • Private key: Used for decryption and must be kept secret.

Imagine a door that can be locked with one key but unlocked only with another. The key used to lock the door (public key) isn’t important—it can be shared freely, as it’s only useful for locking. However, the key used to unlock the door (private key) must be kept secure. This ingenious system allows anyone to encrypt data using the public key, but only the intended recipient (with the private key) can decrypt it.

This approach is most commonly used for secure data exchanges over the internet, as it ensures that sensitive information can only be decrypted by the intended recipient.

3. Hashing

Hashing doesn’t use encryption keys at all, and some people don’t even consider it a form of encryption. So, what is it?

Hashing is more like fingerprinting. In fact, fingerprinting is another name for it. It’s a one-way operation that generates a unique "fingerprint" for a given input. Unlike encryption, hashing is not reversible—you can’t retrieve the original data from the hash.

For example, if you hash the content of a book, you’ll get a unique identifier (fingerprint) for that content. If you hash the same book on another computer using the same algorithm, you’ll get the same fingerprint—provided the content hasn’t been altered. However, even a single change in the book’s content (like modifying one character) will produce a completely different hash. And that is expected, because it is not the same book anymore, so it is expected that unique id will be different. This is why hashing is often called "fingerprinting."

Which encryption algorithm is used for passwords?

Now that we have a good understanding of encryption algorithm types, the question arises: which one is commonly used for password encryption? Surely, hashing wouldn’t be suitable, right? After all, it’s a one-way operation and doesn’t allow retrieval of the original data. It seems logical to use symmetric or asymmetric encryption instead.

That’s exactly what I thought when I first encountered this topic. But, believe it or not, hashing is the most common approach for password encryption!

What??? How?? Why?? (my internal thoughts)

Why is hashing so commonly used for password encryption

There are two major reasons why hashing is commonly used for password storage:

1. No encryption key

Hashing doesn’t require an encryption key. This is a big advantage because if there’s no key, there’s nothing to steal. Without a key, there’s no risk of it being used for malicious purposes. Additionally, it eliminates the need for secret key management—a task that would otherwise add complexity to the system.

2. One-way operation

Hashing is a one-way operation, meaning it’s irreversible. Once data is hashed, there’s no way to restore the original information from the hash. This ensures that even if the hash is exposed, the original password cannot be retrieved.

These two characteristics make hashing a strong choice for password storage.

Compared to symmetric and asymmetric encryption algorithms, hashing can be described as: secret forever. And that’s exactly what we want for password storage. We want passwords to remain secret indefinitely, ensuring that no one—not even the system administrators—can read them.

But wait a minute—if we store passwords so that no one can ever read them, how do we validate them? Is it even possible to validate a password if we can’t retrieve it?

How do you validate a password that no one can read?

Let’s consider the scenario where a user wants to log in. The user, of course, enters their password in plain text. Now, the system must validate the entered password against what is stored in the database. However, in the database, we don’t store the password in plain text; we store its hash—a hash that no one can read.

So, the question is: how do we validate the password?

The answer is straightforward: instead of comparing the passwords themselves, we compare their hashes. Here’s how it works:

1. When the user submits their password, the system first hashes the entered password.

2. It then compares this newly generated hash with the password hash stored in the database.

3. If the two hashes match, the password is valid!

Now that we know how to validate a password using hashing, let’s explore some hashing examples.

Let’s see some hashing examples

Let’s imagine our password is the string: secret.

In the table below, you can see the different hash values generated for the string "secret" using various hashing algorithms.

hashing example

Now, let’s try a more random string like: AYhjak9xsGVayRo.

hashing example 2

From the examples above, we can see how a simple word like "secret" is transformed into a much longer string of random characters. You can try it yourself! Search for hashing algorithms in your browser and use the two strings above as examples.

Since hashing is a purely mathematical operation, you should get the exact same hash values as shown above, no matter what computer, operating system, or application you use. Go ahead and try it—you’ll see!

But wait—Can’t the hash be guessed?

You are right! It can be guessed.

If the result of hashing the same string is always the same, then in theory, someone could guess passwords until they find one that matches the hash.

In fact, there’s an even more efficient method than guessing: rainbow tables. These are precomputed tables where every possible string (password) and its corresponding hash value are calculated and stored. Instead of guessing, all an attacker needs to do is search the table for the hash value. Once they find it, they’ll know the original string that generated the hash—in other words, they’ll have the user’s password.

Luckily, there’s a solution to this problem, and it’s called salting.

What is password salting?

Password salting involves adding a random string (called a salt) to a password before hashing. In other words, the salt is combined with the password to create a new string, often referred to as the salted password. This salted password is then hashed instead of the original password.

The process looks like this: password + salt → salted password → hashing algorithm → password_hash

While salting doesn’t change the fact that hashing is a mathematical function—where the same input always produces the same output—it does render rainbow tables ineffective. Why? Because the input (password + salt) has changed, and the precomputed rainbow tables are no longer valid for this new combination.

The power of unique salts

But what happens if we use a different salt for each user? In that case, the hacker would need to create a separate set of rainbow tables for each individual salt. If there are 100,000 users, the hacker would need to generate 100,000 unique sets of rainbow tables.

Given that there are billions of possible passwords, this process becomes nearly impossible—especially if users choose complex or uncommon passwords.

Storing salts

This approach sounds great, but there’s one practical question: if each user has their own unique salt, where should it be stored?

Fortunately, there are a few different options. Let’s take a look at the possibilities.

Password hash storage formats

As mentioned earlier, there are multiple ways to store a salt for a user’s password. One common approach is to store it in a separate database column. At first glance, this seems intuitive and straightforward, but let’s examine some drawbacks.

First, storing salts in a separate column adds an extra layer of complexity, as there’s now another column to manage in the database. While this isn’t overly burdensome, it does require additional care.

A much bigger concern is that the salt must never be accidentally updated. If the salt changes, the stored password hash becomes invalid unless the password is rehashed with the new salt. In such cases, both the updated salt and the new hash must be saved simultaneously—or not at all.

Lastly, because plaintext passwords are not stored, it’s impossible to regenerate a password hash if the salt is accidentally modified. This adds another layer of complexity to salt management.

Clearly, saving salts in a separate column comes with its challenges. Fortunately, there’s a better approach: storing the salt together with the password hash.

Storing salt with the hash

So, how does this work? Imagine a single, long string where the first portion contains the salt, and the rest contains the password hash. This simple format eliminates the need for a separate column and avoids many of the issues associated with managing salts.

Many hashing algorithms, such as bcrypt, follow a format called the Modular Crypt Format (MCF). Although not strictly standardized, this format allows for additional information—such as the salt and hash—to be stored together in a single string.

Here’s an example of bcrypt’s Modular Crypt Format:

In this example, four pieces of information are stored:

1.) Hashing algorithm version: $2a$ (indicates bcrypt is used)

2.) Cost factor: 12 (indicates 2^12 = 4096 rounds of hashing)

3.) Salt: ns85ykvqsk.idsbM7Qmha. (22 characters)

4.) Hash: /GMV6HOsOPywnTjtW9IA5GUDm74o71e (31 characters)

This kind of storage format has several advantages. It allows easy identification of the hash algorithm used, which is particularly useful if multiple algorithms are used or during migration. It also provides flexibility, enabling the storage of additional information like salt and cost within the same field. Finally, it simplifies migration, as all relevant data—salt, hash, and algorithm details—are stored together.

In our particular case, it enables us to migrate it seamlessly.

Finally, how to migrate password encryption algorithm seamlessly

Now that we have a clear understanding of password management, we’re ready to migrate a password encryption algorithm seamlessly.

The main idea

The goal is to migrate without the user noticing anything. As mentioned earlier, if passwords were stored in plain text, migration would be simple—but that would defeat the whole purpose of keeping passwords secure. Since passwords are hashed, we need to find a way to retrieve their plain text values temporarily during the migration process.

However, in order to migrate it to another algorithm, the password in plain text has to be known. The question is how?

Thankfully, certain user actions, like logging in or changing their password, involve the user entering their password in plain text. These moments are the only times the plain text password is available and can be rehashed with the new algorithm.

As log-in is the most frequent password flow, let’s use it as an example. The logic is straightforward:

1. When a user logs in, validate their password using the old algorithm.

2. If valid, rehash the password with the new algorithm and save it.

3. From that point forward, the user will use the new algorithm exclusively.

Now, let’s see the implementation details.

The user model

Let’s start by looking at a basic User model:

In this model:

  • The password_hash column stores the hashed password in the database.

  • The password= method hashes the given password and saves the hash to the password_hash column.

  • The valid_password? method hashes the provided password and compares it to the stored hash to validate the password.

Step 1: Update the password setter

First, ensure that all new passwords are hashed using the new algorithm. This can be done by updating the password= method in your User model, like in the example below.

Step 2: Update the password validation logic

Next, modify the valid_password? method to validate passwords based on their hashing algorithm. The simplest way to achieve this is by checking the format of the stored password hash. For example, if the hash starts with the new algorithm’s identifier, validate it using the new algorithm. Otherwise, validate it with the old algorithm and, if valid, rehash the password using the new algorithm and save it. Very simple!

Check the example below.

Step 3: Wait for full migration

That’s it! There is no step 3!

Once all users have logged in and their passwords are rehashed using the new algorithm, you can safely remove the old algorithm from your codebase.

In the case the old algorithm needs to be removed before all users have migrated, you can assign random passwords to accounts still using the old algorithm. These users would then need to reset their passwords using the "Forgot Password" flow. However, if the old algorithm is still secure and there’s no urgency to remove it, you can simply wait until all users migrate naturally.

That is it. Happy coding! With this approach, migrating password encryption algorithms can be done smoothly and securely.

Spread the word:
Keep readingSimilar blogs for further insights
Java Design Patterns: Tips, Challenges, and Best Practices
Technology
Vladimir Š.9 min readDec 20, 2024
Java Design Patterns: Tips, Challenges, and Best PracticesJava Design Patterns may seem complex at first, but once you grasp their underlying principles, they can help you organize your code for better scalability, make it easier to maintain, improve efficiency, and more.
A Comprehensive Guide to Testing API Calls with Jest
Technology
Max L.9 min readDec 12, 2024
A Comprehensive Guide to Testing API Calls with JestFor developers building web apps, mobile apps, or microservices, testing API interactions is a critical step to ensure stability and reliability. In this blog, we’ll dive into testing API calls using Jest—a powerful and versatile JavaScript testing framework.
How JavaScript Signals Are Changing Everyday Development
Technology
Hrvoje D.5 min readNov 7, 2024
How JavaScript Signals Are Changing Everyday DevelopmentSignals are getting popular lately, but why is that? Read the blog to discover how signals in JavaScript are transforming code to be more concise, readable, and understandable.