#2466·faker

en_CA.ssn() can start with 0 or 8, and neither is a personal SIN

Author: feiiiiii5Created Sep 27, 2026Updated Sep 27, 2026

en_CA.ssn() can start with 0 or 8, and neither is a personal SIN

The provider reverses the checksum steps to build a SIN, and the code says as much:

python
# faker/providers/ssn/en_CA/__init__.py:44-48
# This function reverses the checksum steps to create a random
# valid nine-digit Canadian SIN (Social Insurance Number) in the
# format '### ### ###'.
def ssn(self) -> str:
    # Create an array of 8 elements initialized randomly.
    digits = self.generator.random.sample(range(9), 8)

sample(range(9), 8) draws the first digit from {0..8}, so the leading digit comes out uniform over nine values and 9 never appears at all. Measured over 6000 values:

first digit: {'0': 659, '1': 648, '2': 671, '3': 652, '4': 683,
              '5': 672, '6': 675, '7': 657, '8': 683}      9: never
starting with 0 or 8: 22.37%   (2/9 = 22.2% if uniform)

Neither of those two leading digits belongs to a personal SIN:

  • 8 is reserved for the BN (business number) issued to corporations, not individuals.
  • 0 is CRA-assigned individual tax numbers, temporary tax numbers or adoption tax numbers — again not a SIN.

So 0 and 8 are excluded from the province series 1–7, and the temporary-resident series is 9. stdnum encodes exactly that:

python
# stdnum/ca/sin.py
if number[0] in '08':
    raise InvalidComponent()
return luhn.validate(number)

which puts faker's Canadian SINs at 1524/2000 accepted (76.2%). Every rejection I looked at begins with 0 or 8:

017 255 647    814 075 669    023 468 259    850 233 776

I am confident about 0 — several independent validators treat a leading 0 as structurally invalid, since those numbers are not issued to individuals. For 8 the sources agree too (Wikipedia's SIN article states it is used exclusively for business numbers, and DataVeil's SIN mask treats 0/8 as invalid unless you tick an option), though a couple of validators only mention 0, so I have flagged the split rather than overstate it.

Worth noting that faker's own checksum() helper in the same file cites the Wikipedia article as its source, so the reference that documents the restriction is already the one this provider points at.

Why the test does not catch it

tests/providers/test_ssn.py:219 checks the length, that the characters are digits, and the Luhn check:

python
def test_ssn(self):
    for _ in range(100):
        sin = self.fake.ssn()
        assert len(sin) == 11
        assert sin.replace(" ", "").isdigit()
        assert ca_checksum(sin) == int(sin[-1])

The checksum is right in all of these — the provider is careful about that — so nothing in the test looks at which series the number claims to be from.

A fix

Draw the leading digit from the series that a personal SIN can actually have — 1–7, plus 9 if you want to cover temporary residents, which range(9) currently excludes entirely. The rest of the algorithm can stay as it is; the check digit logic looks correct and I could not fault it.

Separately: at 22% of output affected this is frequent enough to matter for anyone using en_CA for test fixtures, which is the point of the provider.

Provenance: main at 975dd2a1 and released faker==40.39.0. I searched the open issues and open PRs and found nothing on this; #763 is a 2018 report about the provider returning malformed output under Python 3, which is a different symptom and is closed.