Skip to content

Pass Tech 3.0.0 — rewritten in Kotlin

Latest

Choose a tag to compare

@gitubpatrice gitubpatrice released this 24 Sep 13:23

Read this first: 3.0 is a different app, not an update

It installs beside Pass Tech 2.x, not over it. Different application id, so
Android treats them as two apps and your 2.x vault is never touched.

To move across:

  1. Open 2.x → Settings → export an encrypted .ptbak backup.
  2. Install 3.0, create your vault, import the .ptbak.
  3. Check your entries are all there.
  4. Only then remove the old app.

Nothing is stranded either way. 3.0 reads every .ptbak ever written — v1, v2
and v3 — and the file it writes still imports back into 2.7.1, both directions
pinned by test vectors the Flutter code itself produced.

Needs Android 8.0 or later. 2.x ran on Android 7; if that is your phone,
stay on 2.7.1.

Why rewrite at all

The Flutter app wrapped a random secret that did not depend on your password.
One unwrap, and an attacker could carry the vault files off the phone and try
passwords on a machine with as many GPUs as they liked.

That is gone. The secure hardware now takes part in every single attempt:

pwHash   = Argon2id(password, salt, m = 19 MiB, t = 2, p = 1)
hw       = HMAC-SHA256(slot key, domain || pwHash)     computed INSIDE the Keystore
finalKey = HKDF-SHA256(salt, pwHash || hw, info, 32)
file     = AES-256-GCM(finalKey, nonce, plaintext, aad)

The slot key never leaves the chip. A guess can only be checked on this
phone
, one at a time. Copy the files elsewhere and there is nothing to attack.

Plausible deniability, rebuilt

Three vault slots now, not two. All three exist from first launch, all three are
the same size, and the ones not in use hold random data encrypted under a key
that exists nowhere — neither you nor we can ever open them.

The part that took the longest: when you write to one vault, the others are
padded to match even though the open session holds no key for them. Random
bytes are appended after the ciphertext, and the GCM tag decides where the real
data ends on the way back. So file sizes never say which slots are in use, and
no existing vault needed converting.

Also new

  • The address check reads nine browsers, and an entry can declare several
    addresses.
  • Five languages — English, French, German, Italian, Spanish — chosen in the app.
  • Backup import from Chrome, Edge, Bitwarden JSON and CSV, with a size limit on
    every field so a hostile file cannot exhaust memory.
  • The clipboard is cleared by an alarm that wakes the phone, and only ever takes
    back what this app put there.

Audited before release

A security audit over the whole application: eight findings kept, all fixed.
Each fix was then checked by putting the defect back and confirming the right
tests went red — a test that stays green when the bug returns was never testing
the bug. 648 tests, detekt and lint green.

Then two real phones, which found two defects no test could have caught: the
clipboard cleaner wiped text it did not own, and a debug build showed the same
launcher name as the release, so three measurements had been made on the wrong
app without anyone noticing.

What is still not guaranteed

Stated plainly in THREAT_MODEL.md, because a security promise
that is not bounded is not a promise:

  • How many slots are free can be counted from inside the app, by someone
    holding your unlocked phone. With three slots this cannot be closed — every
    way of hiding the refusal tells the same thing sooner. Rate-limited like a
    wrong password, and stated as R6.
  • Some manufacturers' keyboards keep their own clipboard history, which no
    app can reach. Measured on a Galaxy S24: the value was still in the keyboard's
    suggestion bar a minute after the system clipboard had been emptied. R8.
  • A rooted phone reads any app's memory, this one included.

Pass Tech 2.7.1 is now end of life

No further work, security fixes included. Its download stays up rather than
being taken down, for one reason: it runs on Android 7 and 3.0 does not.

Verification

One APK, and that is a change worth explaining. The 2.x releases shipped four —
three per-processor builds plus a universal one — because Flutter carried
several MB of native code per architecture. Without Flutter, the native code
left is about 60 KB of AndroidX in a 3.9 MB file. Splitting would save around
1%, so one file installs everywhere and there is nothing to choose.

Signed with the same certificate as every Pass Tech release since 2.6.0:

certificate SHA-256  733cf8b4ffb0ce052ca92604872f781aa29effd38c36cb710faf94d6b3c35794

Permissions in the built APK, not read from the source manifest: INTERNET
(update check and the optional breach check, in k-anonymity), USE_BIOMETRIC,
USE_FINGERPRINT. Nothing else.

f68d680d42d479a10c263fae680eed7ded338d5c1c6db3d16236832266a500f5  pass-tech-universel-3.0.0.apk