The BACS hash: HMRC's payment check that sounded good and caught nobody
- From 2012 to 2023 every FPS for a Bacs-paid employee had to carry a SHA-256 hash of the payment (data item 118).
- HMRC only ever got the hash and the amount back from Bacs. It never saw who was paid.
- The "random" part of the hash had 59,319 possible values, so identical payments collided constantly.
- It was switched off by one email in February 2023, mid-year. The law behind it is still in force.
When RTI came in, HMRC added a field to the Full Payment Submission called data item 118. It was a 64 character SHA-256 hash. If you paid staff by Bacs your payroll software had to work it out for every employee, every payday, and send it to HMRC.
The idea was that HMRC could cross-check the hash from your payroll software against a hash Bacs worked out from the actual payment. If they matched, the wages you said you paid were the wages you actually paid.
It sounds good. In reality it couldn’t tell who was being paid, it produced the same hash for different payments all the time, and in eleven years nobody has ever pointed to a single thing it caught. Then in February 2023, halfway through the tax year, HMRC retired it with one email. It still had to be computed for the rest of the year, because they’d decided too late to take it out of the schema!
Where it came from#
In 2009 David Gauke (shadow Treasury minister, then the actual minister) sat down with payroll software people with a big idea: what if the banks worked out your tax? You’d send your gross payroll to a “centralised deductions” system, it would take off PAYE, NI and student loan on the way through, keep the tax and forward the net pay. Payroll software basically stops existing in that world.
HMRC consulted on this in July 2010, alongside a tamer idea called Real Time Information. The industry’s response to centralised deductions was basically “absolutely not”, and it got dropped. RTI survived, mainly because Universal Credit needed a real time feed of what people earned.
But once you drop the banks doing the deductions, you lose the one thing that gave you for free: knowing the money reported is the money that actually moved. RTI on its own is just an employer telling HMRC “trust me, I paid Dave £1,800”. So someone asked how do we know the FPS matches the Bacs payment, and the answer was a hash.
What it actually was#
If you paid staff by Bacs Direct Credit under your own Service User Number (SUN, the ID that says “this business submits to Bacs directly”) you had two things to do:
- In field 7 of the Standard 18 Bacs payment file, put a small random tag.
- On the FPS for that employee, put a SHA-256 hash made from that tag plus the payment details.
VocaLink (who run Bacs) would work out the same hash from the payment as it went through, send it to HMRC, and HMRC would check the two matched.
The hash input is an ASCII string made of four parts stuck together: the 4 character sub-reference from field 7, the originator’s sort code, the recipient’s sort code, and the amount in pence padded to 11 digits. Have a go. It starts on HMRC’s own worked example from the guidance, so the first thing you see is the published hash checking out.
If you’d rather do it in a terminal:
$ printf '/A..10000030914400000125671' | sha256sum
a8e88f215cc98f40a2d0c47c49d0b09f4593d9bb81aef118202987a8cc0e3689
That matches the published hash exactly.
Two things about that input matter a lot.
The “4 character sub-reference” is really 3 characters. The first one is always a forward slash, which has to sit in character position 32 of the Bacs file. The three after it are the actual random bit, and each comes from an alphabet of hyphen, full stop, forward slash, 0-9 and A-Z. That’s 39 possible characters in each of 3 slots, which matters for the collision maths below.
The account number isn’t in there. Try changing it in the calculator above. Nothing happens, because it was never part of the hash. HMRC confirmed this in an FOI response.
What HMRC actually received#
In 2013 Adrian Kennard (a software developer and ISP owner who blogs as RevK, and ran his own payroll through his own software) put in a Freedom of Information request asking HMRC to say exactly what Bacs sent them. The answer:
VocaLink regenerates the hash cross reference, when they receive the payment instruction and send the (1) hash cross reference and (2) the related payment amount to HMRC… VocaLink do not provide any other information about individual payments to HMRC, and HMRC does not have access to the Bacs source information or bank account details.
So HMRC gets the hash and the amount. That’s it. They never see who was paid. No account number, no name, nothing that links the payment to a person.
If the employer picks the random tag, the employer picks the amount, and the payment can go to any account, what’s being checked? RevK put it bluntly on his blog at the time:
The payment can be to anyone (even yourself, which is perfectly valid as a BACS payment). […] It checks no more than the submitter has access to BACS. […] Real fraudsters have no problem as it is simple for them to pass the tests for the hash.
To get a matching hash all you have to do is make a Bacs payment of the right amount from the right sort code with the right tag. Pay it to yourself. Pay it into the company’s own account. The hash matches, the FPS “reconciles”, and not a penny needs to have reached the employee you reported.
When RevK put this to HMRC in a follow up FOI, asking them to just admit the hash “does not prove a payment to the individual concerned, and so is totally pointless”, they basically refused to talk about it. They also refused to say what the whole thing had cost.
The collision problem#
Everything above is sourced. This bit is mine.
HMRC’s guidance leans on the word “unique”. It says the combination of sub-reference, sort codes and amount gives “a sufficient degree of uniqueness to allow effective matching”, and that when someone gets paid the same amount regularly, the random sub-reference is what tells the payments apart. So the sub-reference is doing all the work. How much can it actually tell apart?
39 characters in 3 positions gives 39 × 39 × 39 = 59,319 distinct
sub-references. That’s log₂(59,319) ≈ 15.9 bits, so less than a 16-bit
number. The “random token” securing payroll reconciliation for the whole
country had less entropy than a short.
Now apply the birthday problem, the same maths that says 23 people in a room gives you a 50/50 chance of two sharing a birthday. In any pool of payments that share a sort code pair and an amount, how many do you need before two land on the same sub-reference and produce hashes HMRC can’t tell apart?
At 287 otherwise identical payments it’s a coin flip. By 600 it’s over 95%.
By 1,000 it’s basically certain. (The exact product crosses 50% at k = 287,
which matches the standard √(2N ln 2) estimate of 286.8.)
Is a pool of hundreds of identical payments realistic? For one small employer, no. But HMRC wasn’t matching within one employer, it was matching everything coming through Bacs nationally, and Bacs handles roughly 27 million payments a day (6.86 billion in 2025 over about 252 working days). Round number salaries going through common high street sort codes aren’t rare, they’re the norm. RevK noted in 2013 that HMRC had already admitted they’d get duplicates, having left the one genuinely unique field, the Bacs Service User Number, out of the hash completely.
So there are three separate problems with it:
- The thing meant to tell payments apart can’t reliably tell them apart.
- The hash only proves the sender can use Bacs.
- HMRC never sees who was paid.
What it cost other people#
Before RTI, field 7 of the Standard 18 format was basically unused. Suddenly every employer submitting under their own SUN had to write a random value into it on every payment, and their bank’s Bacs software had to let them. That cost money. A 2013 survey by the CIPP and Payroll Alliance (156 respondents, 71% of them paying staff by Bacs) found 42% of the Bacs payers had been charged extra to get field 7 into their file. Of those, 39% paid between £1,000 and £4,999 and 8% paid between £7,000 and £30,000.
Some banks’ software wouldn’t do it at all. RevK’s bank, Lloyds, looked at upgrading its Lloydslink Bacs bureau to support field 7, decided (in his words) “it would cost too much”, and shut the bureau down instead, with about two months notice, at the end of February 2013. He moved to a Bacstel-IP setup through another bureau at a bit over £2,000 a year, and later reckoned “hundreds of Lloydslink customers had to change DD collection” because of it. That’s his account and his estimate. I haven’t found Lloyds’ side of it, and the closure could have had other reasons too.
And hardly anyone it was aimed at was even paying by Bacs in the relevant way. When the proposal came out in 2011, Tom McClelland of the payroll vendor 12Pay said that of his roughly 10,000 client companies, not one paid employees through their own Bacs SUN. Most UK employers pay through internet banking or Faster Payments, and neither of those has a hash at all. Yes, over 90% of the UK workforce is paid by Bacs Direct Credit, but that’s because they work for big employers. The long tail of small employers, the ones RTI most wanted to keep an eye on, mostly never went near it.
HMRC’s response to employers who didn’t use Bacs was to warn developers that filing without a hash would be treated as a “compliance investigation issue”.
How it ended#
By 2017, when HMRC published its forty page RTI Post-Implementation Review, the hash gets mentioned exactly once. Not under fraud detected or revenue protected. It’s in a bullet list of stakeholder complaints, four words long:
BACS hash matching is problematic
Then on 13 February 2023 HMRC’s Software Developers Support Team sent round an email (Simon Parsons at PAYadvice kept a copy):
HMRC has recently decided to withdraw the requirement for employers to supply an entry in the Bacs hash code field (data item 118) for 2023-24 onwards. Unfortunately this decision was made too late to remove it from the 2023-24 RTI FPS schema; any supplied values will not be processed.
Too late to take the field out, so it stayed in the schema for another year. Software kept computing it and HMRC ignored it.
There’s also a loose end that still isn’t tied off. Regulation 67CA of the Income Tax (PAYE) Regulations 2003, the legal basis, is still in force as I write this. legislation.gov.uk shows “no known outstanding effects”, it’s never been revoked. Meanwhile data item 118 has been removed from the current Data Items Guide completely. I searched the 2025/26 version and the words “hash”, “Bacs” and the item number just aren’t there. So there’s a legal duty with no field to put it in.
Things I worked out myself#
The collision maths is one. Three smaller things from following the story to the end:
Nobody can point to it ever working. I went looking for a single example of the hash doing its job: a tribunal decision citing a mismatch, an NAO value for money line, a compliance case, anything in the Post-Implementation Review. There’s nothing. That’s not proof it never flagged anything, but for something mandatory, in legislation, that cost banks and employers real money, I’d expect at least one example.
Faster Payments made it irrelevant anyway. The hash only existed because Bacs has a file with a field 7 to hide a token in. Faster Payments, which is how a growing share of smaller employers pay staff, has no such field and never had a hash. As more employers moved to Faster Payments, fewer and fewer payments were covered by it anyway.
ISO 20022 probably killed it. The withdrawal email gave no reason. But the timing lines up with Bacs moving towards ISO 20022 messaging under the New Payments Architecture, where the flat, fixed position Standard 18 record (“solidus in character position 32” and all) doesn’t have a home. You can’t mandate “field 7, positions 33 to 35” of a file format you’re retiring. I can’t prove this was the trigger, it’s an inference, but “the file it lived in is being replaced” explains a silent withdrawal a lot better than HMRC changing its mind about fraud. They never conceded the fraud point even when RevK put it to them directly in 2013.
What it was really for#
Here’s what the check could and couldn’t see. Pick what the employer does and compare the two hashes HMRC would have received.
If it didn’t catch fraud, what was it for? I think it was there to make people feel better. It let HMRC tell Universal Credit’s designers, and themselves, that RTI figures were “verified against actual bank payments”, which sounds great in a business case.
The lesson for developers is the usual one: a hash is only as good as the least trusted input to it, and every input here was picked by the person being checked. SHA-256 worked fine. It hashed an amount the employer chose, a sort code the employer controlled and a random string the employer made up, and HMRC couldn’t see the account number, which was the one thing that mattered.
Sources#
Primary (HMRC / legislation):
- HMRC guidance: Generating the cross reference (RTI hash) - now marked “Withdrawn”; contains the algorithm, the alphabet, and the worked example whose SHA-256 digest is verified above.
- Regulation 67CA, Income Tax (PAYE) Regulations 2003 - the legal basis, still in force as at September 2026.
- The Income Tax (PAYE) (Amendment No. 2) Regulations 2012 (SI 2012/1895) - inserted 67CA, effective 1 September 2012.
- Improving the operation of PAYE / Real Time Information (policy background) - the 2010 consultation and the centralised-deductions proposal that RTI grew out of.
- RTI Programme Post-Implementation Review (2017) - the sole official reflection: “BACS hash matching is problematic.”
FOI (Adrian Kennard / WhatDoTheyKnow):
- What information do BACS send to HMRC? - HMRC confirms it receives only the hash and the amount, and never sees bank details.
- How much has the BACS hash cost? - HMRC declines to give costs; contains RevK’s on-record argument that the hash proves nothing.
Contemporary commentary:
- RevK: PAYE RTI BACS madness (Jan 2013), BACS Submissions (Mar 2013) and BACS/RTI hash (May 2013) - the Lloydslink bureau closure and his move to another bureau, the “pay yourself” hole, and the acknowledged duplicates. The “hundreds of customers” figure is his, from a comment on the May post.
- AccountingWEB: Employers hit by BACS costs for RTI (2013) - the CIPP / Payroll Alliance RTI Software Costs Survey: 42% of Bacs payers charged extra for field 7, upgrades of £1,000 to £30,000.
- AccountingWEB: HMRC hashes up RTI compromise (Aug 2011) - the “kludge” reaction and 12Pay’s “not one of 10,000 clients pays by Bacs SUN”.
- PAYadvice (Simon Parsons): HMRC drops the ‘best practice’ BACS Hash (Feb 2023) - the withdrawal email in full, and the David Gauke origin story.
- Pay.UK Bacs statistics and Bacs Direct Credit introduction - annual volumes and the “over 90% of the UK workforce” figure.
This describes a payroll obligation that ran from September 2012 to April 2023, plus my own analysis where flagged. Not tax advice. The collision arithmetic is reproducible from the figures given; the ISO 20022 explanation is my inference, not an HMRC statement.
Comments