I have these notes left over from when we had elections in December last year, I was curious about how the whole receipt thing worked and what gets encoded in there. With the iVote system you confirm your status as a registered voter for your electorate, then login to the iVote website with your password, and cast your vote with a secret number that’s sent to your email address.

After voting you get a receipt identifier code, and apparently you can use this to confirm that your vote was recorded, but only for a few hours after voting. Why? I have no idea.

My receipt code is: URMNG4XHWA
This is apparently short for a real (much longer) receipt identifier, which you see when you look it up: URMNG4XHWAZBJLJB2TLTHBXXOU3FG342BPHJ3HSBENCFGZT6PWMQ====

That’s clearly base64 or base32. But there’s only 21 different letters here (no D/I/K/V/Y), and 4 different digits (2/3/4/6). That’s an alphabet of only 25 symbols, which is a weird. 32 would make sense, so you could encode 5 bits per symbol. This is like 4 bits per symbol plus some noise.

To be fair, my receipt code is probably not a pangram so base32 is a fair guess. Assuming it is, and we keep the padding at the end… actually we do get something well-formed!

[furinkan@suomi] ~ $ echo URMNG4XHWAZBJLJB2TLTHBXXOU3FG342BPHJ3HSBENCFGZT6PWMQ==== | base32 -d | hexdump -C
00000000 a4 58 d3 72 e7 b0 32 14 ad 21 d4 d7 33 86 f7 75 |.X.r..2..!..3..u|
00000010 36 53 6f 9a 0b ce 9d 9e 41 23 44 53 66 7e 7d 99 |6So…..A#DSf~}.|
00000020

32 bytes of real data, which is 256 bits – maybe the SHA256 of the input. The receipt also includes a “control code”, which is completely unexplained.

Your control code is:
KTzpDWTwufarm2MK39hFewAMUO3yLU04 o1q2cNUQVQbRqwn7TieLOD4cT/e8v3y TBQ9hll2YGi/UBLLY4KFPC+NUXOB0kz RdXWBOKgZnP5GP3STSKDkgFyzvE1LV0 WncbPHv017HFd7zEFmmhOiDWPrz93q8 FVhZNenA4JXO/ldaiCh4AEZ/GfjxPxc ZrlkLqUoUQczi6jr1ejyq2hPf+v/lCa 9lodGeApcGcrpjPVK+StR/SbhxotSUH jvh+MysGUYblJp0b69qLGC343ZB13FU LfE+m3UKxXGgw+U5m0YVqeN6hQoBEWV qlOXpJuWwJxv4OO7c4HWZZPukWLJrPQ ==#qHxQuQ9Oy7ec/T9UZe2M3uEDiqSM YA3dUKsJ2c2CULM=#ff8080827cfd07 28017cfd211f810854#ff8080827cfd 0728017cfd211f570841#1638237459 406

Let’s fix up those chunks. The first 344 chars is obvious base64, which produces 256 bytes of plaintext. It looks random. The rest of the data seems to be delimited by hashes, which gives us another base64 string, two 32-char hex strings, and what looks like a unix timestamp.

// 44 bytes of base64 -> 32 bytes plaintext, looks random
#qHxQuQ9Oy7ec/T9UZe2M3uEDiqSMYA3dUKsJ2c2CULM=

// 16 bytes of hex-encoded data per string, or maybe counters of some sort
#ff8080827cfd0728017cfd211f810854
#ff8080827cfd0728017cfd211f570841
// Almost certainly a unix timestamp
#1638237459406

We run the timestamp through an online calculator and get…
Your time zone: Tuesday, 30 November 2021 12:57:39.406 PM GMT+11:00 DST
That’s about 24 minutes ago, which is about when I cast my vote.

There’s also a QR code for verification, and this is a bit more telling. I scanned it in and grabbed the encoded data, it carries a lot of JSON that could probably be better packed, but oh well.

{
  "electionId": "ff8080827cfd0728017cfd211f810854",
  "institutionId": "ff8080827cfd0728017cfd1fa5760006",
  "randomness": {
    "0": 47, "1": 178, "2": 95, "3": 89, "4": 227, "5": 232, "6": 113, "7": 25,
    "8": 50, "9": 180, "10": 247, "11": 220, "12": 255, "13": 79, "14": 141, "15": 12,
    "16": 27, "17": 109, "18": 138, "19": 81, "20": 115, "21": 102, "22": 64, "23": 235,
    "24": 196, "25": 246, "26": 58, "27": 34, "28": 7, "29": 213, "30": 171, "31": 85
  },
  "randomnessId": "tJ0ayl+s6fxiozL6TC3kLCC3cQnO8dYN96DM/RDcjSo=",
  "url": "https://verify.ivote.nsw.gov.au/invote-voter-portal",
  "apiKey": "2OHBKM3T7RYBTXJ25SGP6ZPU5XU2RVXM",
  "credentialManagerUrl": "https://verify.ivote.nsw.gov.au/nswec-extended-login/login",
  "delegatedIdentityUrl": null,
  "credentialManagerCaptchaEndpoint": "https://verify.ivote.nsw.gov.au/nswec-extended-login/needCaptcha"
}

Aha, that explains the two hex strings we saw in the control code earlier! One of them anyway, the electionId is a match. The institution is probably “NSW local councils” or something like that, or maybe the institution in the QR code is my local council itself. If I could gather other people’s verification QR codes, both inside and outside my electorate, I could probably have a guess at what it means.

Then we’ve got 32 entries of random, and I’m pretty sure each one is an unsigned byte value. randomnessId is a base64 string, encoding 32 bytes of plaintext. Same number of bytes of randomness that we’ve got here. Does it mean anything?

echo 'tJ0ayl+s6fxiozL6TC3kLCC3cQnO8dYN96DM/RDcjSo=' | base64 -d | hexdump -C
00000000  b4 9d 1a ca 5f ac e9 fc  62 a3 32 fa 4c 2d e4 2c  |...._...b.2.L-.,|
00000010  20 b7 71 09 ce f1 d6 0d  f7 a0 cc fd 10 dc 8d 2a  | .q............*|

It doesn’t look like it means anything. I wonder what apiKey is, is it just there for free use? Can I poke a REST API with it? It’s 32 bytes of base32, so 160 bits – that could be a SHA1 hash. Or it could just be a randomly generated secret string, like API keys tend to be.

I got hungry and lost interest at this point, I don’t think there’s much else to be gleaned here haha.

Conclusions

Is iVote a good system? Is it secure? I don’t know. The impression I get is that it’s probably decent, and they’re probably doing a bunch of things right. But it only takes a couple of small mistakes to break the security of a system like this.

Online voting is complex, it is a really hard problem. Look at the criteria you need to fulfil:

  • Eligible people (voters) are able to vote
  • Ineligible people are not allowed to vote
  • Voters can only cast a single ballot, to prevent ballot stuffing
  • Ballots must be kept a secret, to prevent voters being coerced
  • Voters must be confident that their vote has been recorded, so they can’t be discarded by a poll worker attempting to skew the outcome
  • Voters ideally can’t prove to anyone else who they’ve voted for
  • Voting should be available for the entire period that the polls are open, so that everyone who wants to vote can vote
  • Vote counting should not be able to influenced by anyone involved in the counting process
  • The electorate must be confident that all votes were counted with a high degree of accuracy
  • and probably many other things I’ve forgotten right now

Perhaps more importantly, did the system work? I voted a few days ahead of the election, which was held on December 4th, and had no problems at all with iVote. I have friends that tried to sign up and use iVote on the day of the election and they said it was slow or unaccessible due to load. It is definitely a good thing that you could sign up on the day of the election, and that voting was open for a period of time ahead of the election day, not just a single day. It is a fundamental principle of democracy that everyone has fair access to voting, which means you must work to remove arbitrary constraints on that.