oljasz.com / field notes
Entry 013 of 016 / B Security
Warsaw
Last entry 07.07.2026
No. 013SDR for SIGINT / Part 2 of 5

Reading My Water Meter With a $30 SDR - and the Factory Key Nobody Changed

Wireless M-Bus, a one-line DSP bug that produced zero decodes for a day, and the uncomfortable discovery that a whole neighbourhood of smart water meters ships with the same factory AES key.

Filed
Reading
5 min
Section
B / Security
Tags

My water meter talks. Every few minutes it transmits its reading over Wireless M-Bus on 868.95 MHz. So does yours, probably. This post is about reading my own meter with a cheap SDR, a frustrating bug that cost me a day, and a finding that says more about the state of IoT security than about radio.

First, the boring legal bit, because it matters: passively receiving radio is legal in the EU. Reading my own meter is unambiguously fine. The interesting part is what passive listening reveals about how these devices are secured, which is the actual point of the post.

01The setup

Wireless M-Bus (wM-Bus) is the protocol most European utility meters use for drive-by and fixed-network reading. On 868 MHz it comes in T1 and C1 modes. The tooling is mature:

  • rtl_wmbus - demodulates wM-Bus frames from raw I/Q
  • wmbusmeters - parses frames into readings, handles decryption

The canonical pipeline is one line:

Listing 1 / text
rtl_sdr -f 868.625M -s 1.6e6 - | rtl_wmbus -s | wmbusmeters --format=json stdin:rtlwmbus

I had this running inside the SDR hub from the previous post. And it decoded nothing. For a day. Zero frames, while the burst hunter clearly saw energy on the meter channel.

02The one-line bug: a DC spike in the wrong place

Here is the trap. My hub captured at 868.9 MHz (a wide center, good for the band-wide hunter) and then digitally shifted down to the wM-Bus center before handing samples to rtl_wmbus.

Every RTL-SDR has a DC spike - a strong artefact right at the tuner's center frequency. When I digitally shifted the spectrum, I dragged that DC spike from 868.9 to land almost exactly on the T1 channel at 868.95. The meter's signal was buried under my own receiver's artefact. rtl_wmbus -o removes the spike at true center, not one I had relocated on top of the data.

The fix was to stop being clever: tune the dongle's hardware center directly to 868.625 MHz, which is exactly where rtl_wmbus -s expects it. No digital shift, DC spike sits harmlessly between the channels, done.

I proved it the honest way. I captured raw I/Q, ran both paths offline against the same recording: the 868.9-plus-shift path decoded zero frames; the native 868.625 path decoded my meter immediately. Same data, same decoder, different center. The lesson generalises: when a decode tool specifies a center frequency, it usually has a reason. Give it what it asks for before you optimise.

With that fixed, my meter read out cleanly - cubic metres, matching the dial. Cross-checking the reading a few hours later showed it had ticked up by exactly the litres we had used. Real data, off the air, no wires.

03The uncomfortable part

My meter is a Sensus iPERL. Its frames are AES-128 encrypted. So how did wmbusmeters decrypt mine?

Because the iPERL ships with a global factory default key, and the water utility never changed it.

That is not a secret I am revealing - the key is documented in hobbyist guides online, precisely because so many deployments leave it untouched. I tried it against my meter. It worked. Then, out of curiosity about the scale of it, I let the decoder run against everything the rooftop antenna could hear.

The same factory key decrypted every iPERL water meter in radio range - eleven of them. An entire local deployment, all reading out with one published default key.

I am not listing the serial numbers, and I am not publishing anyone's consumption. That would be a privacy violation regardless of what the radio makes technically possible, and it is not the point. The point is the systemic one:

A default credential shipped across an entire fleet is not encryption. It is the appearance of encryption.

Anyone within a few hundred metres, with a $30 dongle and free software, can read the lot. The encryption is real AES; the key management defeats it entirely. This is the smart-meter version of every "admin/admin" router story, except it is rolled out to a whole neighbourhood by an organisation that should know better.

04What responsible looks like here

The constructive move is disclosure to the utility, not a serial-number dump on a blog. Per-meter keys exist in the iPERL provisioning flow - they just have to be used. The fix is operational, not technical: provision unique keys, rotate the default. I am writing up the finding for the operator with the "what" and the "why" and none of the neighbours' data.

If you want to read your own meter:

Listing 2 / text
wmbusmeters --format=json rtlwmbus MyWater iperl <your-id> <key>

Your meter ID is on the dial and in the first decoded frame. Whether the global key works on yours tells you something about how your utility runs its key management. It is a legal, one-command security audit of your own infrastructure.

Next in the series: decoding LoRa that sits below the noise floor, and the 80-line numpy demodulator I wrote to do it.

Passive reception is legal in the EU. Reading your own meter is fine. Reading and publishing other people's consumption is a privacy violation - do not. This post documents a systemic key-management failure responsibly, without exposing anyone's data.

Written by

Paweł Oljasz

Engineering Leader - infrastructure, security, AI, embedded/IoT and SDR. 15+ years in production; leads a team of senior specialists. This notebook is where the lab work gets written down.

Cite this entry
@misc{oljasz2026readingwater,
  author       = {Oljasz, Pawe{\l}},
  title        = {Reading My Water Meter With a \$30 {SDR} - and the Factory Key Nobody Changed},
  howpublished = {Lab notebook, entry 013},
  year         = {2026},
  month        = jul,
  url          = {https://oljasz.com/blog/reading-water-meters-sdr}
}

Entry 013 / filed 01.07.2026 / stable URL, no tracking parameters.