Halt import on control characters or a ':'/'/' inside an issuer or account (they collide with the OATH issuer:account separator and period/ prefix), on a composed name over 64 bytes with a clearer per-field message, and on a digit count outside 6-8 or a non-positive period.
3.0 KiB
otp-import
Import TOTP credentials from an exported spreadsheet into a VivoKey Apex OTP (ykneo-oath) applet over PC/SC.
The importer reads the curated columns of an export spreadsheet
(otp-keys-export.xlsx: Issuer, Account, Secret, Type, Algorithm, Digits,
Period) and writes each credential to the applet using the standard YKOATH
PUT command. It authenticates first if the applet is password-protected, asks
before overwriting credentials that already exist, and can optionally profile
per-credential on-chip memory consumption via the javacard-memory applet.
The protocol layer is verified against the applet sources
(com.vivokey.otp.YkneoOath / OathObj) and doc/Protocol.txt. See
docs/design.md for the full design and protocol notes.
Security
This tool handles live TOTP secrets. Keep your secrets out of this
repository. The spreadsheet, any CSV, and QR screenshots are excluded by
.gitignore; keep the actual data in a private location (e.g. a synced
personal folder) and point the importer at it with --input.
Requirements
- Python 3.9+
pyscard,cryptography,openpyxl- A PC/SC reader with the Apex (or a JavaCard carrying the applet) presented
Usage
# 1. Validate the data and preview the APDUs (no card needed)
python apex_otp_import.py --input /path/to/otp-keys-export.xlsx --dry-run
# 2. Read-only: list what is already on the applet
python apex_otp_import.py --input /path/to/otp-keys-export.xlsx --list
# 3. Import (asks before overwriting existing credentials)
python apex_otp_import.py --input /path/to/otp-keys-export.xlsx
# Non-interactive conflict handling
python apex_otp_import.py --input ... --overwrite-all
python apex_otp_import.py --input ... --skip-existing
# Target a specific reader, supply a password, and profile memory
python apex_otp_import.py --input ... --reader VivoKey --password '...' --profile-memory
# Import, then set an access password (only if the applet has none yet)
python apex_otp_import.py --input ... --set-password 'yourpassword'
# Reset: wipe all credentials and any password (standalone; no input needed)
python apex_otp_import.py --reset
Authenticating to a password-protected applet uses the standard YKOATH scheme
(PBKDF2-HMAC-SHA1 of the password with the device id as salt, then mutual
challenge-response). --set-password only sets a password on an applet that has
none; it does not change or remove an existing one. --reset clears everything.
The input is validated before any reader is touched. The run halts with a
per-row report if it finds: duplicate credential names; invalid Base32;
over-long names or keys; an unsupported algorithm; control characters or a
:// inside an issuer or account (they break the OATH name convention); or a
digit count outside 6-8 / a non-positive period. Fix the source data and re-run.
Tests
Hardware-free unit tests cover the protocol encoding, password derivation, input validation, and response reassembly:
python test_oath.py