Hash a CSV Column
Pick the columns holding an email, a phone number or a customer id, choose SHA-256, SHA-512, SHA-1 or MD5, add a salt if the result is going anywhere near another team, and the digests appear beside the originals. Everything happens inside this browser tab, so the identifiers you are hiding are never uploaded in order to hide them.
No file to hand? Grab the messy sample, or press Try an example for a five-row contact list.
A hash without a salt is a pseudonym
There is a conversation that happens in a lot of companies. Somebody asks for a customer extract. Somebody else says the emails cannot go out. A third person says fine, we will hash them, and everyone relaxes. The extract leaves with a column of 64-character digests in it and the meeting moves on.
That column is not anonymous. A hash function is public and deterministic. Anyone who receives f8808b6d6bf2020c... can take a list of email addresses they already hold, run the same SHA-256 over each one, and see which digests line up. The attack costs nothing, needs no cleverness, and works because the space of plausible email addresses is small. The same applies to phone numbers, which are worse: every mobile number in a country fits comfortably in a table you can build in an afternoon.
The Salt field is what turns the digest into something the recipient cannot reverse. Type a string that only your side knows, set Salt position to Before the value or After it, and the digest becomes specific to your export. A different salt per export is better still: it stops two files you sent to two partners being joined against each other behind your back.
Keep a record of the salt. Digests are one way, so if the string is lost, the mapping back to your own customers is lost with it. I write the salt into the ticket that asked for the export, not into the export.
Worked example: five contacts, one hashed column
This is the file behind Try an example, saved as contacts.csv:
id,full_name,email,company,signed_up
001,Ada Lovelace,ada@analytical.example,Analytical Engines Ltd,2024-01-15
002,Grace Hopper,grace.hopper@navy.example,Naval Systems,2024-02-02
003,Katherine Johnson,kj@orbital.example,Orbital Mechanics Inc,2024-02-19
004,Alan Turing,alan@bletchley.example,Bletchley Research,2024-03-04
005,Radia Perlman,radia@spanning.example,Spanning Tree Networks,2024-03-21
Tick email under Columns to hash and leave everything else where it starts: SHA-256, Hex, no salt, original kept. The result appears without a Run button, because there is no Run button:
id,full_name,email,email_sha256,company,signed_up
001,Ada Lovelace,ada@analytical.example,f8808b6d6bf2020cd454cdedf04664557bfa3416af336dea45bd292d04f97563,Analytical Engines Ltd,2024-01-15
002,Grace Hopper,grace.hopper@navy.example,6773a15b8a300e1d8439fe072ed2f66fad9540b241dec0741a332f2c3d797bd8,Naval Systems,2024-02-02
003,Katherine Johnson,kj@orbital.example,fbe506a325889189678b015d4fe0cd63bce56ff32bf5f74fd24ea0ec3564275d,Orbital Mechanics Inc,2024-02-19
004,Alan Turing,alan@bletchley.example,de51d2cfcd0c4001e37afefc32c81fa32efa2949e82195d7b54b818c0b4a1572,Bletchley Research,2024-03-04
005,Radia Perlman,radia@spanning.example,60543f27db00dcc3b6beacad8d4bfbf66f3682540078986af165c92c66f8d287,Spanning Tree Networks,2024-03-21
Four things are worth noticing. The new column is called email_sha256 and sits immediately to the right of its source, not at the end of the row, so the pair reads together in a spreadsheet. The id column still says 001, because cells are strings from the moment the file is read until the moment it is written. The stats strip says 5 distinct values digested, which on a real file is a useful surprise. And there is a warning under it, in plain words, telling you the digests are reversible from a wordlist because the salt is empty.
Now type 2024-q1-export into Salt. Every digest changes completely:
id,full_name,email,email_sha256,company,signed_up
001,Ada Lovelace,ada@analytical.example,ae133b4c9f9b9cf4cf4f295196a159534027d88faae372816b16e7e9f1bdc3d4,Analytical Engines Ltd,2024-01-15
002,Grace Hopper,grace.hopper@navy.example,fff24c1859854acd9c820370e59dabe35149099db037fcb470fd39aac495da98,Naval Systems,2024-02-02
The warning about the missing salt disappears with it. Switch Original column to Replace it and the raw addresses are gone from the output entirely, with the digests sitting in the email column under its original name. That is the setting for a file you are actually sending out. The download is called contacts-hashed.csv, so the file you started from is still on disk untouched.
Four algorithms, and why MD5 is still here
SHA-256 is the default and the right answer nearly always. SHA-512 produces 128 hex characters instead of 64; on a 64-bit machine it is not slower, but the column is twice as wide and nothing in a CSV join gets safer for it. SHA-1 and MD5 are both collision-broken, which means somebody can deliberately construct two inputs sharing a digest.
That matters enormously for a signature and not at all for a join. If your warehouse already stores an MD5 of a customer id and you need a CSV whose keys line up with it, MD5 is not a compromise, it is the requirement. The page states this every time you pick one of the two, in one sentence, and then does what you asked.
The three SHA variants go through crypto.subtle.digest, the browser's own implementation, which is compiled code rather than JavaScript and does not freeze the tab on a large file. WebCrypto deliberately does not implement MD5, on the grounds that it is not a security primitive any more, which is correct and also unhelpful when you need one. So MD5 lives in about seventy lines on this page, working on bytes rather than characters, which means a cell holding café hashes its UTF-8 encoding and matches what every other MD5 implementation on earth would give you.
Hex or base64, and why you might want the shorter one
Output switches between the two renderings of the same digest bytes. Hex gives 64 characters for SHA-256, is case-insensitive, and is what nearly every database and every other hashing tool prints. Base64 gives 44 characters for the same digest, which is a third narrower and matters when the digest is going into a URL, a cookie, or a column somebody has to eyeball.
Base64 is case-sensitive and contains + and /, so it is not safe to drop into a query string without further encoding. It also carries trailing = padding: an MD5 in base64 comes out as b+geE2sUXFH4Koax3W1kXw==, 24 characters against MD5's 32 in hex. If two systems disagree about a hash, the encoding is the first thing to check, well before the algorithm. The same digest in two renderings looks like two completely different values.
Whichever you choose, the salt is applied to the text before any of this happens, so switching between hex and base64 changes the presentation and not the underlying hash.
Things that quietly break a hashed join
- A trailing space.
ada@example.comandada@example.comare different inputs and produce digests with nothing in common. Run Trim Whitespace over the key column in both files first. - Letter case. Email addresses are treated as case-insensitive by most mail systems and as case-sensitive by every hash function. Lowercase the column on both sides with Change Case before hashing.
- A different salt. Obvious in writing, invisible in practice, because a mismatched salt produces perfectly valid digests that simply never match. If a join returns zero rows, compare the salt and its position before you suspect anything else.
- A different encoding. One side in hex and the other in base64 gives the same zero-row join with no error message anywhere.
- Blanks. Empty cells stay empty rather than becoming the digest of the empty string, which is deliberate, and does mean a blank key still joins to nothing.
- 100 MB is the ceiling here. Past that the widget points you at the full editor, which streams instead of holding the whole file in memory.
What the page does that a minimal hasher does not
The usual version of this tool offers one column, three algorithms, and a checkbox to keep the original. It works. Four things here go further, and each of them exists because of a real failure I have watched happen.
- A salt, with a position. Without one you are shipping a lookup table with extra steps.
- More than one column per pass. Contact files carry a name and an email and a phone number, and hashing them one at a time means three downloads and three chances to change a setting between them.
- WebCrypto rather than hand-rolled JavaScript. A hand-written SHA-256 loop is synchronous and will lock the tab for seconds on a large file. The browser's own implementation will not.
- Deduplicated digests. Hashing 100,000 rows that hold 400 distinct ids does 400 digests, and the strip tells you it did.
Frequently Asked Questions
Is a hashed email address anonymous?
No. A hash of a bare email address is a pseudonym, not anonymization. The set of email addresses a person might have is small enough to guess through: take a wordlist of known addresses, hash every one with the same function, and match. The Salt field is the fix. With a secret salt that only your team holds, the same address produces a digest nobody outside can reproduce, and the page warns you on every run where the salt is empty.
Which algorithm should I choose?
SHA-256 unless something else already decided for you. It is the default, it is fast because the browser implements it natively, and 64 hex characters is short enough to live in a spreadsheet column. Pick MD5 or SHA-1 only when you are matching digests that another system already produced. Pick SHA-512 when a policy document says so; it is not meaningfully safer here and the column is twice as wide.
Why does the page keep telling me MD5 is broken?
Because it is broken for signatures and fine for matching, and the two get confused. Anyone can construct two different inputs with the same MD5 digest, so MD5 cannot prove a file is untampered. Nothing about that stops it joining two exports on a shared customer id. Use it to match, never to protect, and the warning is there so nobody forgets which of those they are doing.
What happens to blank cells?
They stay blank. The digest of the empty string is a real constant value, and writing it would give every customer with no phone number the same fake identifier, which then joins them all together downstream. A blank in means a blank out, and the stats strip counts how many cells were skipped that way.
Can I hash more than one column at once?
Yes. Columns to hash is a multi-select, and every ticked column is processed in the same pass with the same algorithm, encoding and salt. A contact file with a name, an email and a phone number goes through once and comes back with three digest columns, each named after its source.
Will the same value hash the same way in two different files?
Yes, as long as the algorithm, the encoding, the salt and the salt position are the same, and the source text is byte for byte identical. That is the whole point of hashing a join key. Watch for trailing spaces and case differences, because Ada@example.com and ada@example.com are different inputs and produce completely different digests. Trim and lowercase the column first if the two files disagree about either.
Does hashing a hundred thousand rows take a hundred thousand digests?
No. Identical values are hashed once and the result reused, so a file with 100,000 rows and 400 distinct customer ids costs 400 digests. The stats strip reports the number of distinct values it actually digested, which is often the first hint that a column has far less variety in it than you assumed.
Is the file uploaded to hash it?
No. The SHA family runs through WebCrypto, which is the browser's own hashing code, and MD5 runs in about seventy lines of JavaScript on this page because WebCrypto deliberately refuses to implement it. Both execute inside your tab. There is no upload endpoint, so the identifiers you are trying to protect are never sent anywhere in order to protect them.
Related
Salt it, then send it
Free, no account, no upload. Tick the column, type a salt, take the file.
Back to the hasher