Skip to main content

Cardputer as a Hardware Password Manager

For the past month, I've been prototyping my own hardware password manager using the Cardputer, a compact device that's surprisingly perfect for the task. 


I learned about the Cardputer while searching for the ideal hardware to build a password manager. It's essentially a microcontroller (the ESP32-S3) packed with a screen and a keyboard. What really sold me on it were these features:

  • Direct Interaction: I can interact directly with the device – typing my master password, searching for credentials, and confirming password entry, all on the Cardputer itself.
  • USB Keyboard Emulation: The device can seamlessly emulate a USB keyboard, allowing for both manual and automatic password entry into other devices.
  • Hardware-Accelerated Crypto: The ESP32-S3 boasts hardware acceleration for AES and SHA operations, crucial for secure password management.
  • Secure Element Integration: The Cardputer supports an optional ATECC608B secure element (available as an official accessory), providing additional cryptographic capabilities, secure key storage, and true random number generation.

Taming the Cardputer


The official development framework is ESP-IDF, but after some research I decide to use esp-hal. I took it as a nice opportunity to learn Rust and embedded systems.

My journey with esp-hal quickly turned into a hands-on crash course in embedded systems development. I wasn't just writing high-level application code; I was crafting drivers from the ground up.

This meant diving into the specifics of the Cardputer's hardware. I spent most of my time implementing drivers, not quite at the level of manipulating memory addresses directly, but still very much involved in the low-level details. This included tasks like scanning the keyboard's button matrix and translating physical presses into logical key events, as well as working with communication protocols like I2C and SPI to interact with peripherals.

Simultaneously, I delved into the world of cryptography, deepening my understanding of common algorithms and concepts like AES modes of operation, key derivation functions (KDFs), and secure hashing. This project became a fantastic opportunity to connect the theoretical aspects of cryptography with practical implementation.

This project also sparked an exhilarating, and at times challenging, adventure in learning and applying Rust. I deliberately chose the no_std and no_alloc path, embracing a truly bare-metal approach. This decision plunged me into the deep end of embedded Rust, where every byte of memory matters. It was both a blessing and a curse: while it demanded careful resource management, with everything either stack-allocated or statically allocated, it also provided unparalleled control and efficiency. Along the way, I encountered some of Rust's more… idiosyncratic features, wrestling with async function traits, navigating the intricacies of generic types, and occasionally resorting to seemingly superfluous .map(|x| x) closures to appease the borrow checker and its lifetime requirements.


Securing the Secrets

The prototype is functional. While the source code isn't ready for release yet, I'd like to share some of the key design decisions.

For data serialization, I chose FlatBuffers. Its zero-copy nature is particularly well-suited to the no_alloc environment of this project, minimizing memory overhead. The main database structure is inspired by KeePass. Note that the database is read-only on the device; entries cannot be added, modified, or removed.

The database is encrypted using AES-GCM-SIV. The encryption key is derived via HKDF, with the input keying material (IKM) composed of three distinct components:

  • User's Master Password: The output of PBKDF2 applied to the user's master password, using a randomly generated salt.
  • eFuse Key in ESP32S3: The result of HKDF-Expand using a pre-defined message, keyed by random bytes securely burned into an ESP32-S3 eFuse. This ties the encryption to the specific hardware.
  • ATECC608B Private Key: The output of HKDF using a pre-defined message, keyed by a shared secret. This secret is established through ECDH key exchange between the ECC key stored securely within the ATECC608B and an ephemeral key pair generated by the encryptor (e.g., a host computer or another device).
Furthermore, when either or both of the hardware-based secrets (eFuse and ATECC608B) are enabled, they also contribute to individual password encryption. A separate key is derived from these secrets, using the password's sequence number within the database as the IKM for HKDF. This approach, inspired by KeePass's in-memory protection, provides an additional layer of security, though its benefit in this specific embedded context might be less pronounced than in a traditional software environment.

Okay, I might have gone a little overboard with the encryption. The eFuse and ATECC608B key derivation steps are probably not strictly necessary. But hey, it was fun to build! And even though secure elements aren't magic security shields (they do have known vulnerabilities), they make it way harder for someone to steal your passwords.


Final Thoughts

The Cardputer has proven to be a surprisingly capable platform for this secure password manager project, though not without its limitations.


What I liked:

  • Affordability: The Cardputer's low cost is a major advantage, making this project accessible.
  • Well-Suited Hardware: The hardware feature set is almost ideal – providing all the necessary functionality (display, keyboard, secure element, connectivity) without unnecessary extras.
  • Small Attack Surface: The firmware footprint is relatively small (~300KB), minimizing the potential attack surface.
  • Fast Boot Time: The device boots up quickly, making it convenient to use.
  • Rust Ecosystem: The Rust ecosystem, with its package manager (cargo) and readily available crates, made development fast, easy, and enjoyable.

Areas for Improvement:

  • Performance Limitations: The ESP32-S3's processing speed is a bottleneck, particularly for computationally intensive operations like PBKDF2-SHA512, which takes approximately 5 seconds for 65536 rounds. On the other hand, the performance is more than enough after the unlocking
  • Documentation Gaps: The documentation for both the ESP32-S3 and ATECC608B could be improved. I encountered significant challenges setting up flash encryption, secure boot, and the undocumented KDF command in the ATECC608B.
  • ATECC608B-TNGTLS Limitations: The Cardputer uses the ATECC608B-TNGTLS variant, which, unfortunately, does not enforce I/O encryption. This means communication between the ESP32-S3 and the secure element is not as secure as it could be.
  • Known Vulnerabilities: Both the ESP32-S3 and ATECC608B have known vulnerabilities. While these don't necessarily render the device insecure, they highlight the importance of a strong master password as the primary defense.
  • Missing RTIC Support: The lack of RTIC support on the ESP32-S3 is a missed opportunity for learning and exploration.
  • RustCrypto Hardware Acceleration Limitations: RustCrypto's AES implementation can effectively leverage hardware acceleration when available, but that does not seems true for the HMAC, HKDF, and PBKDF2 implementations. I believe it's related to the dependency on the Clone trait.
  • ECC Algorithm Support: The ATECC608B focuses on ECDSA curves and algorithms. Support for Ed25519 would be nice.

Comments

Popular posts from this blog

Determine Perspective Lines With Off-page Vanishing Point

In perspective drawing, a vanishing point represents a group of parallel lines, in other words, a direction. For any point on the paper, if we want a line towards the same direction (in the 3d space), we simply draw a line through it and the vanishing point. But sometimes the vanishing point is too far away, such that it is outside the paper/canvas. In this example, we have a point P and two perspective lines L1 and L2. The vanishing point VP is naturally the intersection of L1 and L2. The task is to draw a line through P and VP, without having VP on the paper. I am aware of a few traditional solutions: 1. Use extra pieces of paper such that we can extend L1 and L2 until we see VP. 2. Draw everything in a smaller scale, such that we can see both P and VP on the paper. Draw the line and scale everything back. 3. Draw a perspective grid using the Brewer Method. #1 and #2 might be quite practical. #3 may not guarantee a solution, unless we can measure distances/p...

[转] UTF-8 and Unicode FAQ for Unix/Linux

这几天,这个东西把我搞得很头疼 而且这篇文章好像太大了,blogger自己的发布系统不能发 只好用mail了 //原文 http://www.cl.cam.ac.uk/~mgk25/unicode.html UTF-8 and Unicode FAQ for Unix/Linux by Markus Kuhn This text is a very comprehensive one-stop information resource on how you can use Unicode/UTF-8 on POSIX systems (Linux, Unix). You will find here both introductory information for every user, as well as detailed references for the experienced developer. Unicode has started to replace ASCII, ISO 8859 and EUC at all levels. It enables users to handle not only practically any script and language used on this planet, it also supports a comprehensive set of mathematical and technical symbols to simplify scientific information exchange. With the UTF-8 encoding, Unicode can be used in a convenient and backwards compatible way in environments that were designed entirely around ASCII, like Unix. UTF-8 is the way in which Unicode is used under Unix, Linux, and similar systems. It is now time to make sure that you are well familiar ...

Moving Items Along Bezier Curves with CSS Animation (Part 2: Time Warp)

This is a follow-up of my earlier article.  I realized that there is another way of achieving the same effect. This article has lots of nice examples and explanations, the basic idea is to make very simple @keyframe rules, usually just a linear movement, then use timing function to distort the time, such that the motion path becomes the desired curve. I'd like to call it the "time warp" hack. Demo See the Pen Interactive cubic Bezier curve + CSS animation by Lu Wang ( @coolwanglu ) on CodePen . How does it work? Recall that a cubic Bezier curve is defined by this formula : \[B(t) = (1-t)^3P_0+3(1-t)^2tP_1+3(1-t)t^2P_2+t^3P_3,\ 0 \le t \le 1.\] In the 2D case, \(B(t)\) has two coordinates, \(x(t)\) and \(y(t)\). Define \(x_i\) to the be x coordinate of \(P_i\), then we have: \[x(t) = (1-t)^3x_0+3(1-t)^2tx_1+3(1-t)t^2x_2+t^3x_3,\ 0 \le t \le 1.\] So, for our animated element, we want to make sure that the x coordiante (i.e. the "left" CSS property) is \(...