#totp #2fa #hmac #otp #authentication #command-line #command-line-tool

app totp-qr

Display the optauth URI and TOTP token from QR images, URI, or otpauth-migration link

4 releases

0.2.1 Oct 21, 2023
0.2.0 Oct 20, 2023
0.1.1 Oct 18, 2023
0.1.0 Oct 18, 2023

#748 in Command line utilities

34 downloads per month

MIT license

93KB
1K SLoC

totp-qr   Crates.io Crates.io

Command line utility to extract otpauth strings from QR-images and generate their respective TOTP

Why? I need the text SECRET encoded in previously saved QR images, screenshots, and Google Authenticator Export images for import into other apps KeePassXC, Proton Pass

The motivation for this project was initiated by password housekeeping. I'm content with the tools I use for password management such as KeePassXC, pass, iTerm2 Password Manager (can't live without now), but I lacked visibility and portability of my TOTP parameters and passwords.

This tool uses:

  • The excellent rqrr crate for digging out otpauth data from most image types
  • The reverse engineered protobuf
  • The file-format crate for classifying stdin
  • Shout out to totp-rs for its succinct byte slicing and Algorithm Enum; derivations of both were used. MIT LICENSE

Project Status:

  • Waiting for resolution on SIGPIPE for general CLI Unix tools to avoid "broken pipe".
  • The remedy is to modify stdout and use writeln!(stdout)? instead of println!() as shown below
pub fn reset_sigpipe() -> Result<(), Box<dyn std::error::Error>> {
    #[cfg(target_family = "unix")]
    {
        use nix::sys::signal;

        unsafe {
            signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigDfl)?;
        }
    }

    Ok(())
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // behave like a typical unix utility
    general::reset_sigpipe()?;
    let mut stdout = io::stdout().lock();
    ...


TOTP-QR

Using totp-qr in a shell function to securely view tokens

  1. Install totp-qr e.g. cargo install totp-qr or build cargo install --path .
  2. Gather your QR-images into a directory
  3. Run scripts/mk-totp-func.sh directory
  4. Inspect, copy, and add to your ~/.bashrc

Creating the shell function, walk-through

The images directory contains 2 example QRs

  1. otpauth-totp-qr.jpg holds 1 account: "otpauth://totp/..."
  2. otpauth-migration-qr.jpg holds 3 accounts: "otpauth-migration://offline?data=..."
$> totp-qr --uri images/otpauth-totp-qr.jpg
otpauth://totp/Example:alice@google.com?issuer=Example&period=30&secret=JBSWY3DPEHPK3PXP

$> totp-qr --uri images/otpauth-migration-qr.jpg
otpauth-migration://offline?data=Ci0KCkhlbGxvId6tvu8SEnRlc3QxQGV4YW1wbGUxLmNvbRoFVGVzdDEgASgBMAIKLQoKSGVsbG8h3q2%2B8BISdGVzdDJAZXhhbXBsZTIuY29tGgVUZXN0MiABKAEwAgotCgpIZWxsbyHerb7xEhJ0ZXN0M0BleGFtcGxlMy5jb20aBVRlc3QzIAEoATACEAIYASAA

otpauth data should be kept PRIVATE, OpenSSL can be used to encrypt the data (password: foo)

$> totp-qr --uri images/* | openssl aes-256-cbc -e -pbkdf2 -a
enter AES-256-CBC encryption password:
Verifying - enter AES-256-CBC encryption password:

U2FsdGVkX18lfKZ20uQn/AcAWa85hUmcJzQ8mvS9JX0BJb7qVDddrCjbjPxagIw6
hwHeLBPWx1U0GbA7zszAYKNa6FB2I53ldNET/tnutUBNmQeuxqbiVH8A0or9Ni8+
Lj8onivfmaGzcBGGGMtz3wliD/LL+iUhkG+A2FZpIE2mIf9QdwofI9jSAhDhAW3y
d+AXZWWsHRRVs5MvIA++CcchKLG+FOza3fcBIt7RtqkdISQYDw+TgMGLN8NS5/ak
tk8PcuO+QfjmtNXh0/96mn5jYCGdD1NvioeDkwBu7883q2ChHXcOLRuPqqlJAR/2
T+DwgtEyCO5ZhQPn3nj9E1Gy1xXAm+4Yt8CueXvuBS5SJJLQd94Q+HT1SsyMhYB0
FGVb6YifAjV3Snsk3UO/60quJ8cfQxjDW5Pef/a0LjtMZL2d+jaYImFcLEMUrnlI
FRGDcbHR1oAmdonyuSNJBQ==

scripts/mk-totp-func.sh encrypts URI's and outputs a Bash function named totp()

$> ./scripts/mk-totp-func.sh images/
enter AES-256-CBC encryption password:
Verifying - enter AES-256-CBC encryption password:

totp() {
openssl aes-256-cbc -d -pbkdf2 -a << EOF | totp-qr $1 | sort -t, -k2
U2FsdGVkX1+gVAFEnnQVFQVmzDUU47Sl6NIqFOAQaM85dspvn8gt2hueK272RRi4
vdWDBLsFeKM4qp7Jq2TSV2Lca2/29cwPcZtAVnaz02VbxO2m/e3b4RjB9AxjRk1R
iTPdTzG+BO2GYHjdz515Dc/N4+HD5UMVJr7yAsypdJ/ThRN3CWCjUYd3mAGx9/g7
0GCwTJ6psw4CtwbgL3hg66cZq9w43Wwj0P+S3eL87ueZRHHfr10hEtLTsJQLuRDl
479WZpzFFPTyrr3jVQFMqmhgEXKXf2VnFp4aLvCk6OKP93iQU3fE5aRWTEpQytYF
+F/AvpAQUnEOvAAivFFa2SBXZPHDscENzG16P0O8i3hWoyJizoAJIOMOPsA3HgMZ
1kxCFBnME1Pd1dlrSTsfhFNpjfbaURWxI5pwMS/fAKMIoRLWydeGJOukNIv+zPmI
PPJXNNa5fQP647srICuCnw==
EOF
}


Putting it all together

$> ./scripts/mk-totp-func.sh images/ >> ~/.bashrc
$> . ~/.bashrc
$> type -a totp
totp is a function
totp ()
{
    openssl aes-256-cbc -d -pbkdf2 -a <<EOF |
    ...
EOF
  totp-qr $1 | sort -t, -k2
}

Tip: If you're on a Mac using iTerm2 check out password manager (shortcut: ⌥ ⌘ F) for supplying passwords

totp() displays tokens sorted by issuer

$> totp
enter AES-256-CBC decryption password:
757676, Example
757676, Test1
255080, Test2
476239, Test3

totp -e to view account details as JSON

$> totp -e | jq
enter AES-256-CBC decryption password:
[
  {
    "secret": "JBSWY3DPEHPK3PXP",
    "issuer": "Test1",
    "sha": "SHA1",
    "digits": 6,
    "period": 30
  },
  {
    "secret": "JBSWY3DPEHPK3PXQ",
    "issuer": "Test2",
    "sha": "SHA1",
    "digits": 6,
    "period": 30
  },
  {
    "secret": "JBSWY3DPEHPK3PXR",
    "issuer": "Test3",
    "sha": "SHA1",
    "digits": 6,
    "period": 30
  },
  {
    "secret": "JBSWY3DPEHPK3PXP",
    "issuer": "Example",
    "sha": "SHA1",
    "digits": 6,
    "period": 30
  }
]

totp -u to view URI's

$> totp -u
enter AES-256-CBC decryption password:
otpauth-migration://offline?data=Ci0KCkhlbGxvId6tvu8SEnRlc3QxQGV4YW1wbGUxLmNvbRoFVGVzdDEgASgBMAIKLQoKSGVsbG8h3q2%2B8BISdGVzdDJAZXhhbXBsZTIuY29tGgVUZXN0MiABKAEwAgotCgpIZWxsbyHerb7xEhJ0ZXN0M0BleGFtcGxlMy5jb20aBVRlc3QzIAEoATACEAIYASAA
otpauth://totp/Example:alice@google.com?issuer=Example&period=30&secret=JBSWY3DPEHPK3PXP


General Usage

Usage: totp-qr [OPTIONS] [FILES]...

Arguments:
  [FILES]...  image-file|stdin, filename of "-" implies stdin

Options:
  -a, --auth <AUTH>  "otpauth-migration://offline?data=..." or "otpauth://totp/...?secret=SECRET"
  -v, --verbose      Verbose output
  -e, --export       Export account information as JSON
  -i, --import       Import JSON accounts
  -u, --uri          Output extracted URI's
  -h, --help         Print help
  -V, --version      Print version

Verbose Output (-v, --verbose)

$> totp-qr -v images/*.jpg
otpauth = otpauth-migration://offline?data=Ci0KCkhlbGxvId6tvu8SEnRlc3QxQGV4YW1wbGUxLmNvbRoFVGVzdDEgASgBMAIKLQoKSGVsbG8h3q2%2B8BISdGVzdDJAZXhhbXBsZTIuY29tGgVUZXN0MiABKAEwAgotCgpIZWxsbyHerb7xEhJ0ZXN0M0BleGFtcGxlMy5jb20aBVRlc3QzIAEoATACEAIYASAA
237769, Account { secret: "JBSWY3DPEHPK3PXP", issuer: "Test1", sha: "SHA1", digits: 6, period: 30 }
734660, Account { secret: "JBSWY3DPEHPK3PXQ", issuer: "Test2", sha: "SHA1", digits: 6, period: 30 }
021109, Account { secret: "JBSWY3DPEHPK3PXR", issuer: "Test3", sha: "SHA1", digits: 6, period: 30 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
otpauth = otpauth://totp/Example:alice@google.com?issuer=Example&period=30&secret=JBSWY3DPEHPK3PXP
237769, Account { secret: "JBSWY3DPEHPK3PXP", issuer: "Example", sha: "SHA1", digits: 6, period: 30 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$> totp-qr --auth="otpauth://totp/ACME%20Co:john.doe@email.com?secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ&issuer=ACME%20Co&algorithm=SHA1&digits=6&period=30"
970700, ACME Co

Decode from stdin

$> echo 'secret=HXDMVJECJJWSRB3HWIZR4IFUGFTMXBOZ' | totp-qr
970700,

$> totp-qr < images/otpauth-migration-qr.jpg
237769, Test1
734660, Test2
021109, Test3

Import (-i, --import) / export (-e, --export) JSON Accounts

$> totp-qr -e images/*.jpg | totp-qr -iv
939954, Account { secret: "JBSWY3DPEHPK3PXP", issuer: "Test1", sha: "SHA1", digits: 6, period: 30 }
561818, Account { secret: "JBSWY3DPEHPK3PXQ", issuer: "Test2", sha: "SHA1", digits: 6, period: 30 }
787732, Account { secret: "JBSWY3DPEHPK3PXR", issuer: "Test3", sha: "SHA1", digits: 6, period: 30 }
939954, Account { secret: "JBSWY3DPEHPK3PXP", issuer: "Example", sha: "SHA1", digits: 6, period: 30 }

Dependencies

~22MB
~187K SLoC