# totp-qr ![Crates.io](https://img.shields.io/crates/v/totp-qr) ![Crates.io](https://img.shields.io/crates/d/totp-qr)
# 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](https://keepassxc.org), [Proton Pass](https://proton.me/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](https://keepassxc.org), [pass](https://www.passwordstore.org/), [iTerm2 Password Manager](https://iterm2.com/features.html) (can't live without now), but I lacked visibility and portability of my TOTP parameters and passwords.
This tool uses:
* The excellent [rqrr crate](https://docs.rs/rqrr/latest/rqrr/) for digging out otpauth data from most image types
* The reverse engineered [protobuf](https://alexbakker.me/post/parsing-google-auth-export-qr-code.html)
* The [file-format crate](https://docs.rs/file-format/latest/file_format/) for classifying stdin
* Shout out to [totp-rs](https://docs.rs/totp-rs/latest/totp_rs/) for its succinct byte slicing and **Algorithm Enum**; derivations of both were used. [MIT LICENSE](LICENSE)
### Project Status:
* Waiting for resolution on [SIGPIPE](https://github.com/rust-lang/rust/issues/62569) 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
```text
pub fn reset_sigpipe() -> Result<(), Box> {
#[cfg(target_family = "unix")]
{
use nix::sys::signal;
unsafe {
signal::signal(signal::Signal::SIGPIPE, signal::SigHandler::SigDfl)?;
}
}
Ok(())
}
fn main() -> Result<(), Box> {
// 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=..."
```text
$> 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](https://www.openssl.org/) can be used to encrypt the data (password: foo)
```text
$> 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](scripts/mk-totp-func.sh) encrypts URI's and outputs a Bash function named totp()
```text
$> ./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
```text
$> ./scripts/mk-totp-func.sh images/ >> ~/.bashrc
$> . ~/.bashrc
$> type -a totp
totp is a function
totp ()
{
openssl aes-256-cbc -d -pbkdf2 -a < totp
enter AES-256-CBC decryption password:
757676, Example
757676, Test1
255080, Test2
476239, Test3
```
### totp -e to view account details as JSON
```text
$> 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
```text
$> 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
```text
Usage: totp-qr [OPTIONS] [FILES]...
Arguments:
[FILES]... image-file|stdin, filename of "-" implies stdin
Options:
-a, --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)
```text
$> 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 }
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
### Auth link (-a, --auth)
```text
$> 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
```text
$> 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
```text
$> 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 }
```