Picoth - 2FA Auth with Pi Pico

An easy to use yet secure 2FA gadget, using a Raspberry Pi Pico and RGB Keypad, MicroPython.

Similar projects worth following
2FA has quickly become an easy to implement solution for all online services willing to add extra safety to user accounts.
As a result, users are now overwhelmed with 2FA requests for anything.
I need them for Github, Discord, Hosting account, Online shops, bank and exchanges...

For one, I don't feel comfortable with all these keys only stored physically on my phone.
Then, It's a pain for me to unlock, open Google authenticator, scroll to the right item in the ever growing list aso...
For long, I searched some nice and ready made hardware that would allow me to handle that in a more user friendly way.

Then I saw Pimoroni's RGB Keypad. 16 keys with rgb leds...
The thing was part of the Raspberry Pi Pico addons, and the Pi Pico seemed capable enough to handle the task.
So, I picked a Keypad, a Pico Display in the hope of adding extra feedback, and the journey began...


- Easy to use

- Handle many 2FA keys, for different contexts (home/work/dev/banking)

- Reasonable safety if stolen (no clear-text key)

- Form factor allowing single handed operation

- Enough visual feedback to be sure what you do

- USB Keyboard emulation

  • Picoth on Reddit and new features

    Angainor03/25/2021 at 08:36 0 comments

    The Picoth project was posted on Reddit.

    Thanks for the likes and feedback over there!

    Several interesting feedbacks already, like add the possibility to open the related website, for instance on long press. Nice one!

  • Source code released

    Angainor03/24/2021 at 15:15 0 comments

    Fresh off Github:

    Working, but to be improved!

  • Moving from MicroPython to CircuitPython

    Angainor03/22/2021 at 09:37 0 comments

    Micro python had almost everything we needed...

    I had a prototype running and fully working. Just... no USB HID!

    Since I saw no move on that front, was not capable enough to add USB_HID support to MicroPython, I had to choose:

    - No USB HID, the user has to retype the code

    - Move to Circuit Python

    However CircuitPython (CP) is based on an older branch of Micropython (MP) , and comes with different features and architecture. USB_HID is supported on all boards however.

    Whereas with MP you are likely to need to compile your extra libs and features yourself, with CP you can use the precompiled default firmware, since almost all extra libs are provided as .MPY, python bytecode.

    This comes at the expanse of some performance loss, but makes it way easier for fresh users to experiment, since you don't have to compile anything.

    Another strong feature of CP is the default "CIRCUITPY" USB disk that gives R/W access to the board flash.

    For safety reasons however, it's likely we'll have to disable that - as well as the default REPL - for production release.

    Now, for the migration part:

    Pimoroni provided libs for MP, not for CP.

    Also, CP has no support for interrupts nor threads (the Pico has 2 cores)

    Hopefully, many Python devs experimented and gave snippets.

    For instance, the adafruit "DotStar" lib can be used to drive the RGB leds of the keypad,

    SandyJMcDonald wrote an awesome lib for another pimoroni keyboard, supporting decorators and making use of the keyboard very straightforward.

    With that already done, I was able to assemble a working CP library for the Pimoroni RGB Keypad.

    One major difference though: With MP I used interrupts, so the main loop was sleeping by default (most of the time) until a key was pressed. With CP, the main loop is constantly polling the keypad as fast as possible to catch key presses. I don't like that but there is currently no workaround.

    Then came the screen part, Pico Display. CP has a ST7789 library, working with its Displayio abstraction layer. DisplayIO is a quite evolve library, allowing use of groups, scaling, layers, sprites, text....

    On the other hand, it is way slower than the Pimoroni C library.

    I still need to find out if this is due to python vs C alone, or an overhead due to the Display IO layer.

    In the later case, use of lower level framebuffer objects could make it closer to the MP version.

    Keeping the display fast is especially important in our case for several reasons:

    - The OTP code is time sensitive. If it takes 1 sec to display it, this is significant (can be solved by drifting the time by as much as the latency)

    - Since the main loop polls the keypad, anything taking significant time impacts the feeling of the keypad and needs prolonged press on the keys

    As for now, I did remove the live rectangle that was showing the time left before the current code is no more valid to maintain the device responsive.

    Current step is now to reasonably clean up the code, then I could release a first fully working repo!

  • Harsh decision to take

    Angainor02/18/2021 at 19:14 0 comments

    My prototype is now working, and usable no matter the mess in the current code.
    No encryption and lock/unlock yet but that I know to handle.

    The most troublesome issue is the lack of USB_HID with Micropython.
    Yes, circuit python has. But circuit python also comes with a virtual USB drive, exposing the device inner files and is targeting education, toy projects, clearly ruling out production or secure devices.

    Also, circuit python does not have the Pimoroni libs yet. Not a major obstacle, but would mean a rewrite of some parts.

    I would largely prefer to stick with default Micropython, and not rely on a specific fork.

    My options are the following:

    - Stick with mainstream Micropython, no USB_HID for the moment and hope the feature will be ported soon enough. The device is usable thanks to the screen, but frankly, would be so much more friendly with keyboard emulation... I can't imagine Micropython losing all these Pico USB projects in favor of circuit python.

    - Convert the code to circuit python, write wrappers to replicate interfaces similar to the Pimoroni libs I used, to make the port easier.

    What's your take?

    Here is a video of the current code.

    Pages are defined in a config.json file, with TOTP codes to be encrypted.
    2 Pages are defined here, one Test and one "Cryptos", with fake info.

    Every page has 10 codes (using the pad as a regular numpad, not following the Pimoroni keypad numbers)
    Bottom row, left and right of the 0 are previous page/next page.

    Every 0-9 touch is defined with a label, a color and the TOTP code.

    When selecting an Auth, the label and current code are displayed, with the remaining time shown as horizontal bar.

  • Definitive wiring

    Angainor02/10/2021 at 10:31 0 comments

    I now consider the wiring definitive.


    SDA to GPIO10 (i2c1)

    SCL to GPIO11 (i2c1)

    3V3 and GND taken from extra headers soldered on the keypad base.

    RGB Keypad: 

    Default wiring, no change

    Pico Display:

    GPIO 16 (LCD_DC) and GPIO 20 (BL_EN) unchanged

    Reset unchanged, as 3V3 and GND

    LCD_CS connected to GPIO 21

    LCD_SCLK to GPIO 26 (SPI1)

    LCD_MOSI to GPIO 27 (SPI1)


    Buttons and RGB lines, that are on the opposite row can be wired as intended: they are not used by anything else.

    Here is a preview of what it now looks like

    *Update 2021-03-22. Fix wiring.

  • First bumps on the road

    Angainor02/08/2021 at 18:46 0 comments

    It's only once you get the hardware in hand you realize the obstacles on the road :D

    This is also what makes this interesting. Technical challenges to solve? Let tackle them!

    I wanted to use Micropython because I just love Python. RP2040 port of Micropython however, is very, very young. A rocky start they say :)

    - No Native USB support yet on Micropython. Hopefully this will come soon, as circuitpython already has it

    - No RTC module yet. Not critical since for a precise time source I planned to use a DS3231 chip.

    Then I discovered more surprises along the way:

    - Official Micropython has no support for the Pimoroni C libraries (keypad and display) , and support for user c modules is broken

    - Pimoroni's fork does not have sha256

    I ended up handling yet another fork, based upon official micropython with user c modules re-enabled, sha256 and pimoroni libs.

    On the software side, HMAC and more important, SHA1 were also missing...

    Although SHA1 is deprecated, it's still used and required for TOTP...
    There, I chose to use pure python implementations of SHA1, HMAC from micropython-lib, and TOTP.

    Good news, the Raspberry Pi Pico has no ram or speed issue at all computing correct TOTP, even with some code as frozen python code and not C lib. Great!

    Then, real fun began with the hardware itself.

    Pimoroni did a really great job designing and producing all these nifty backpacks and base for the Pico. Some details don't lie, they do it with passion!
    However, one thing I'm absolutely sure they did not plan, was someone willing to use a keypad base AND a Pico display with the same Pico.
    When I first saw the things, I thought you could plug the pico into the base, and the display on top of the pico.
    How naive I was!
    The pico display is supposed to be plugged on the back on the pico, too bad!

    Well, this will not stop me: just plug it on one side with longer, folded headers, and add some wires for the other side?
    Nope!  The RGB Keypad uses i2c0 port, and well as SPI0 port, with GPIO17 as CS.
    The display uses SPI0, with the same CS, MOSI, SCLK as the base.

    Certainly not a reason to despair.
    Diving into the RP2040 docs, I checked the GPIO doc.
    The RP2040 has 2 I2C and 2 SPI ports, plus the GPIO gives some flexibility.

    I ended up with the following setup:
    - No change to the Keypad base nor lib
    - No change to the display Power, reset, BL_enable
    - Move Display SPI pins to SPI1, using free gpios 21, 26 and 27. This required a small change to the ST7789 c library

    I then needed another I2C for the DS3231 RTC. I used GPIO 10 and 11, unused by the keypad base.

    With this setup, both the extensions can be used same time, with no conflict whatsoever.
    One row of curved header physically holds the display at the right angle, and only 3 wires are needed for the TFT to work (buttons and RGB led will need more).

    Current state, with really rough Python code, is able to display the correct 2FA TOTP code from a key, using the DS3231 RTC as clock source. Time remaining before the code is invalid is shown as a shrinking colored bar.

    Next step will be to make sure the wiring is final (it should) and assemble the thing with shorter and definitive wires.
    Then it'll be on the software end:
    - handling decoding of the otp keys
    - clock settings
    - associate physical keys and colors to otp entries

View all 6 project logs

  • 1
    Frankenstein prototype

    Current state is more a quick mashup than a production ready gadget.
    Instructions will be added as soon as something close to definitive is ready.

View all instructions

Enjoy this project?



Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates