<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Hardware Security Key on Schallbert's Blog</title><link>https://blog.schallbert.de/en/tags/hardware-security-key/</link><description>Recent content in Hardware Security Key on Schallbert's Blog</description><generator>Hugo</generator><language>en</language><lastBuildDate>Sun, 18 Oct 2026</lastBuildDate><atom:link href="https://blog.schallbert.de/en/tags/hardware-security-key/index.xml" rel="self" type="application/rss+xml"/><item><title>Use Password manager with hardware security keys</title><link>https://blog.schallbert.de/en/protecting-keepassxc-with-hardware-security-keys/</link><pubDate>Sun, 18 Oct 2026</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/protecting-keepassxc-with-hardware-security-keys/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-10-18-keepassxc-unlock-database-popup-cover.avif"&#10; class="post-cover"&#10; alt="Image: KeePassXC&amp;#39;s database unlock popup as presented on Linux and Windows machines. The option &amp;#39;use hardware key&amp;#39; is ticked."&#10; title="Use Password manager with hardware security keys" /&gt;&#10;&lt;h2 id="the-password-manager"&gt;The Password Manager&lt;/h2&gt;&#10;&lt;p&gt;The web is full of various password management solutions. This is understandable, given that we have an ever-increasing number of accounts, and no one wants to memorize passwords in the hundreds. So, let&amp;rsquo;s get straight to the point:&lt;/p&gt;&#10;&lt;p&gt;From the vast array of options, I evaluated three password managers based on my criteria (secure, user-friendly, open-source, self-managed) to make a final choice:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://support.mozilla.org/en-US/kb/password-manager-remember-delete-edit-logins" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Firefox password manager&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (Online/Offline)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://vaultwarden.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Vaultwarden&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (Online)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://keepassxc.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;KeePassXC&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;/&lt;a href="https://www.keepassdx.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;KeePassDX&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (Offline)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="not-the-firefox-password-manager"&gt;Not the Firefox Password Manager&lt;/h3&gt;&#10;&lt;p&gt;I ruled out Firefox because I want to use my &lt;a href="https://blog.schallbert.de/en/hardware-security-keys-yubikey/"&gt;hardware security key&lt;/a&gt; to unlock it rather than memorizing a long master password. On the plus side, however, it offers great ease of use, automatic form filling, and synchronization capabilities, albeit via third-party infrastructure.&lt;/p&gt;&#10;&lt;h3 id="not-vaultwarden"&gt;Not Vaultwarden&lt;/h3&gt;&#10;&lt;p&gt;I had been focusing on Vaultwarden for a long time. Running it on a local network requires meeting numerous prerequisites and setting up supporting software. Specifically things like &lt;a href="https://helgeklein.com/blog/automatic-https-certificates-for-services-on-internal-home-network-without-opening-firewall-port/#dns-challenge--caddy-to-the-rescue" target="_blank" rel="noopener noreferrer" class="external-link"&gt;HTTPS on the LAN&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; and &lt;a href="https://letsencrypt.org/docs/challenge-types/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ACME challenges&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Consequently, you need a dedicated small computer on your LAN, and ideally, a virtual private server to handle HTTPS traffic termination. Vaultwarden even supports incredibly convenient passkey-based login and can synchronize the password database across all devices on the network.&lt;/p&gt;&#10;&lt;p&gt;Ultimately, however, I avoided the complex setup, its additional dependencies, and software packages, all of which I would have had to maintain.&lt;/p&gt;&#10;&lt;h3 id="keepassxc"&gt;KeePassXC&lt;/h3&gt;&#10;&lt;p&gt;I experimented with various versions of KeePass and eventually settled on KeePassXC. The overall package simply feels more polished and modern; I can use the software across different operating systems, and the extensions needed for hardware security keys (HSKs) are included right out of the box.&lt;/p&gt;&#10;&lt;h2 id="setting-up-keepassxc-with-an-hsk"&gt;Setting up KeePassXC with an HSK&lt;/h2&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-10-18-keepassxc-setup-database-hsk.avif" alt="Image: KeePassXC create new database screen. There is a main password field, a security key field to protect with a local/remote key file, and a Challenge-response field to work with Hardware Security Keys."&gt;&lt;/figure&gt;&#10;KeePass uses the &lt;a href="https://en.wikipedia.org/wiki/HMAC" target="_blank" rel="noopener noreferrer" class="external-link"&gt;keyed-hash message authentication code&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (HMAC) method to unlock the password database using hardware security keys. It relies on a shared secret key that must be present on both sides but is not exchanged between the parties.&lt;/p&gt;&#10;&lt;p&gt;Typically, you generate the secret key on the hardware security key and then copy it into the KeePassXC input field. But let&amp;rsquo;s take it step by step, as the process depends slightly on the specific HSK being used.&lt;/p&gt;&#10;&lt;h3 id="yubikey"&gt;YubiKey&lt;/h3&gt;&#10;&lt;p&gt;Yubico has prepared a guide specifically for &lt;a href="https://support.yubico.com/s/article/Using-your-YubiKey-with-KeePass" target="_blank" rel="noopener noreferrer" class="external-link"&gt;use with KeePass&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. A YubiKey has two configurable &lt;a href="https://docs.yubico.com/yesdk/users-manual/application-otp/slots.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;slots&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; that support various cryptographic functions. From the function selection, choose &lt;code&gt;Challenge-response: program a challenge-response credential&lt;/code&gt;. The YubiKey then generates a &lt;code&gt;20-byte HEX code&lt;/code&gt;, which can subsequently be copied into KeePassXC.&lt;/p&gt;&#10;&lt;p&gt;It is important to note that the displayed code cannot be extracted later. Therefore, if the database is to have a secondary key (which is recommended), the code must be written down and then also stored in the secondary key.&lt;/p&gt;&#10;&lt;p&gt;For the YubiKey, I recommend leaving the &lt;code&gt;requires touch&lt;/code&gt; option unchecked. Otherwise, you have to touch the YubiKey every time you use a password. Even more annoying is that touching the key also becomes necessary when adding or modifying a password entry.&lt;/p&gt;&#10;&lt;h3 id="nitrokey"&gt;Nitrokey&lt;/h3&gt;&#10;&lt;p&gt;Nitrokey specifies that the secret be generated as a &lt;code&gt;20-byte base32 code&lt;/code&gt;. It can store exactly one HMAC secret.&#10;Nitrokey also provides an &lt;a href="https://docs.nitrokey.com/software/nk-app2/keepassxc" target="_blank" rel="noopener noreferrer" class="external-link"&gt;online guide for storing the HMAC secret&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; for use with KeePassXC. As with the YubiKey, this guide relies on using the GUI app.&lt;/p&gt;&#10;&lt;h2 id="yubikey---nitrokey-compatibility"&gt;YubiKey - Nitrokey Compatibility&lt;/h2&gt;&#10;&lt;p&gt;Ideally, I would store the key generated on the YubiKey onto the Nitrokey, or vice versa. That way, I would have a backup key and could access the KeePassXC database using either one.&lt;/p&gt;&#10;&lt;p&gt;But behold, the Nitrokey does not accept the YubiKey&amp;rsquo;s secret:&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-10-18-nitrokey-challenge-response-does-not-accept-yubi-key.avif" alt="Image: The Nitrokey app, being confronted with a YubiKey HMAC secret, returns the error message &amp;#39;Secret is not in base32.&amp;#39; and cannot save the key"&gt;&lt;/figure&gt;&#10;&lt;p&gt;The same thing happens when I try to store a secret generated on the Nitrokey onto the YubiKey:&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-10-18-yubikey-challenge-response-does-not-accept-nitro-key.avif" alt="Image: The Yubikey app, being confronted with a Nitrokey HMAC secret, returns the error message &amp;#39;Invalid format, allowed characters: 0123456789abcdef&amp;#39; and cannot save the key"&gt;&lt;/figure&gt;&#10;&lt;p&gt;I needed a workaround for this. So, taking a pragmatic approach, I created a second password database. Both were intended to hold the same content but use different keys to unlock. I later learned that this wasn&amp;rsquo;t actually necessary. Nevertheless, here are the provisional solutions I came up with initially.&lt;/p&gt;&#10;&lt;h3 id="dead-end-a-cloning-entries-from-the-database"&gt;Dead End A: Cloning entries from the database&lt;/h3&gt;&#10;&lt;p&gt;I populate the first database with passwords, sorting them into groups. Then, I clone a group, open the second database, and drag and drop the clone over.&lt;/p&gt;&#10;&lt;p&gt;Drawbacks: I have to repeat this process for every group, as drag-and-drop functionality isn&amp;rsquo;t supported for the &lt;code&gt;root&lt;/code&gt; group. Whenever I make a change in &lt;code&gt;Database_yubikey&lt;/code&gt;, I also have to update &lt;code&gt;Database_nitrokey&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;This becomes tedious, especially while the database is still under construction.&lt;/p&gt;&#10;&lt;h3 id="dead-end-b-copying-the-entire-database-and-updating-the-hmac"&gt;Dead End B: Copying the entire database and updating the HMAC&lt;/h3&gt;&#10;&lt;p&gt;To do this, you store the HMAC secrets for both hardware security keys (HSKs) within the password manager. When the database changes, you create a copy of the database file, open it, and copy the secret belonging to the &lt;em&gt;other&lt;/em&gt; HSK. Then, you navigate to &lt;code&gt;Database&lt;/code&gt; &amp;gt; &lt;code&gt;Database Security&lt;/code&gt; &amp;gt; &lt;code&gt;Change Challenge-Response&lt;/code&gt; and enter that secret. The database now works with the other key.&lt;/p&gt;&#10;&lt;p&gt;Drawback: Requires manual updating of both databases. The challenge-response setup has to be reconfigured every time.&lt;/p&gt;&#10;&lt;h3 id="solution-converting-the-hmac-secret"&gt;Solution: Converting the HMAC secret&lt;/h3&gt;&#10;&lt;p&gt;I wondered if I could use the command line instead of the app to store the HMAC secret for my Nitrokey. Command-line tools like &lt;code&gt;nitropy&lt;/code&gt; usually offer more options than their GUI counterparts. Perhaps this would allow me to solve the compatibility issue. Here is the &lt;a href="https://github.com/Nitrokey/nitrokey-documentation/blob/main/source/components/source/components/software/nitropy/all-platforms/installation.rst" target="_blank" rel="noopener noreferrer" class="external-link"&gt;link to the nitropy installation guide&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;While looking for suitable documentation, I came across &lt;a href="https://support.nitrokey.com/t/setup-otp-challenge-response-for-keepassxc/5497" target="_blank" rel="noopener noreferrer" class="external-link"&gt;this interesting post about challenge-response&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. It made me realize something that tech-savvy people, or at least those who are used to cryptography, or work close to hardware, have known for a long time:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Data with a size of &lt;code&gt;20 bytes&lt;/code&gt; can be represented as 40 characters in &lt;code&gt;hex&lt;/code&gt; AND as 32 characters in &lt;code&gt;base32&lt;/code&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Consequently, an HMAC secret can be converted and stored on both the YubiKey and the Nitrokey. To do this, I use the small utility &lt;a href="https://linux.die.net/man/1/xxd" target="_blank" rel="noopener noreferrer" class="external-link"&gt;xxd&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; to generate a &lt;code&gt;base32&lt;/code&gt; phrase for my Nitrokey from the YubiKey&amp;rsquo;s hex code.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# open a new terminal&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# make sure the secrets are not saved to history (command for zsh)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;history -p &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# c0ffee = secret hex key&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# xxd convert input to binary (-r), output as plain hexdump (-p)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# interpret as base32 string&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo -n &lt;span style="color:#e6db74"&gt;&amp;#39;c0ffee&amp;#39;&lt;/span&gt; | xxd -r -p | base32&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;YD764&lt;span style="color:#f92672"&gt;===&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# close terminal&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alternatively, it also works the other way round:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# open a new terminal&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# make sure the secrets are not saved to history (command for zsh)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;history -p &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# YD764=== = secret base32 key&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo -n &lt;span style="color:#e6db74"&gt;&amp;#39;YD764===&amp;#39;&lt;/span&gt; | base32 -d | xxd -p&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;c0ffee&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# close terminal&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="keepassxc-keeping-passwords-neatly-organized"&gt;KeePassXC: Keeping Passwords Neatly Organized&lt;/h2&gt;&#10;&lt;p&gt;If you avoid using &amp;ldquo;Log in with Google&amp;rdquo; options for third-party services, you can end up accumulating a large number of passwords over time. To make finding and using these passwords easier on both mobile and desktop, it makes sense to organize them into logical groups.&lt;/p&gt;&#10;&lt;h3 id="password-families"&gt;Password Families&lt;/h3&gt;&#10;&lt;p&gt;After some experimentation, the following naming scheme has proven effective for titles when I need to store multiple related passwords:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;baseapp&lt;span style="color:#f92672"&gt;.&lt;/span&gt;attribute&lt;span style="color:#f92672"&gt;.&lt;/span&gt;specialization&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example, here is how the login details for my &lt;a href="https://blog.schallbert.de/en/gitea-critical-vulnerability-active-exploitation-rce/"&gt;hardened Gitea setup&lt;/a&gt; would look:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea&lt;span style="color:#f92672"&gt;.&lt;/span&gt;login&lt;span style="color:#f92672"&gt;.&lt;/span&gt;schallbert&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea&lt;span style="color:#f92672"&gt;.&lt;/span&gt;login&lt;span style="color:#f92672"&gt;.&lt;/span&gt;testuser&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea&lt;span style="color:#f92672"&gt;.&lt;/span&gt;runner&lt;span style="color:#f92672"&gt;.&lt;/span&gt;registration&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gitea&lt;span style="color:#f92672"&gt;.&lt;/span&gt;recoverykey&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="password-groups"&gt;Password groups&lt;/h3&gt;&#10;&lt;p&gt;I consider the following groups of passwords to be sensible:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Group&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Description&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Devices&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Computer, phone, router, keys&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Finance&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Banks, credit cards, insurances, payment&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Identity&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Email, identity providers, single sign-in&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Shopping&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Online stores, streaming, subscriptions&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Games&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Platforms, launchers, servers&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Social&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Forums, Messengers, Social networks&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Backup&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Recovery keys, licenses, passphrases&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;In doing so, I make sure not to create too many subgroups. Otherwise, it is easy to get confused, especially in the mobile view.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-10-18-keepassxc-password-groups.avif" alt="Image: KeePassXC app with unlocked database. We see different groups of passwords and single entries, each arranged nicely with a logo/symbol image."&gt;&lt;/figure&gt;&#10;&lt;h3 id="icons-"&gt;Icons 🔑&lt;/h3&gt;&#10;&lt;p&gt;KeePassXC offers the wonderful feature of downloading the target website&amp;rsquo;s favicon and using it as an icon. This makes navigating the password manager even easier.&lt;/p&gt;&#10;&lt;p&gt;However, the favicons are all bitmaps. If you prefer to use high-quality vector graphics, you can specify the path to an icon pack under &amp;ldquo;Choose Icon.&amp;rdquo; I downloaded the &lt;a href="https://aegis-icons.github.io/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Aegis icons&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; for this purpose and am very happy with them.&lt;/p&gt;&#10;</description></item><item><title>SSH connection errors</title><link>https://blog.schallbert.de/en/ssh-typical-issues/</link><pubDate>Fri, 12 Jun 2026</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/ssh-typical-issues/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-06-12-ssh-errors-thumb.avif"&#10; class="post-cover"&#10; alt="Image: A list of different SSH error messages. Some very common, but a few have rare causes."&#10; title="SSH connection errors" /&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-06-12-ssh-icon.svg" alt="Image: terminal logo image from Aegis simple image pack, framing the topic of this post."&gt;&lt;/figure&gt;&#10;This short article deals with errors encountered when establishing an &lt;code&gt;SSH&lt;/code&gt; connection. Whether connecting to other computers, servers, or services like version control systems, the error messages and their corresponding solutions are usually the same. However, it is only worth reading further if standard help pages - such as &lt;a href="https://docs.github.com/en/authentication/troubleshooting-ssh" target="_blank" rel="noopener noreferrer" class="external-link"&gt;GitHub&amp;rsquo;s SSH troubleshooting guide&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; - have not provided the desired solution.&lt;/p&gt;&#10;&lt;h2 id="problem-no-route-to-host"&gt;Problem: &amp;ldquo;No Route To Host&amp;rdquo;&lt;/h2&gt;&#10;&lt;p&gt;The console output shows (on a local network):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh 192.168.0.27&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh: connect to host 192.168.0.27 port 22: No route to host&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or (on a remote network):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh 1.2.3.4&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh: connect to host 1.2.3.4 port 22: Network is unreachable&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The cause is very likely an incorrectly entered IP address or a failed DNS lookup. On a local network, a common reason is that the target device is powered off, preventing the router from reaching it. The issue can usually be resolved by correcting the destination address, powering on the target device, or updating the routing table.&lt;/p&gt;&#10;&lt;h2 id="problem-connection-refused"&gt;Problem: Connection Refused&lt;/h2&gt;&#10;&lt;p&gt;Terminal output:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git push gitea&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh: connect to host git.schallbert.de port 22: Connection refused&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fatal: Could not read from remote repository.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this case, a connection to the destination cannot be established at all; the process fails before authentication even begins. In my experience, such errors arise for three reasons:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The host is not yet ready. For example, it might be booting up (the network card is active), but the SSH service has not started. The usual solution here is simply to wait.&lt;/li&gt;&#10;&lt;li&gt;A firewall, rate limiter, or services like &lt;em&gt;fail2ban&lt;/em&gt; reject the connection (&lt;code&gt;REJECT&lt;/code&gt;). This is often caused by too many failed login attempts or an overambitious bot detection mechanism. In rare cases, your IP address might also appear on a &amp;ldquo;deny-list&amp;rdquo; loaded by the target system. In this instance, too, the best course of action is to wait.&lt;/li&gt;&#10;&lt;li&gt;The service in question expects a port other than the standard SSH &lt;code&gt;Port 22&lt;/code&gt;. Consequently, the user, target address, and port do not match.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;The port issue is easily resolved.&#10;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-06-12-gitea-server-configuration-ssh-port.avif" alt="Image: Gitea showing SSH port IDs under /admin/config/Server_Configuration/SSH_Configuration, Port:222, Listen Port: 22"&gt;&lt;/figure&gt;&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Determine the Port on the host (Server Configuration / SSH). The image shows the settings for my Gitea instance.&#10;Alternatively, simply checking the service&amp;rsquo;s configuration file is usually sufficient.&lt;/li&gt;&#10;&lt;li&gt;Specify the correct port number in the SSH configuration file at &lt;code&gt;~/.ssh/config&lt;/code&gt;. Example:&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /.ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Host gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname git.schallbert.de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Port &lt;span style="color:#ae81ff"&gt;222&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="problem-too-many-authentication-failures"&gt;Problem: Too Many Authentication Failures&lt;/h2&gt;&#10;&lt;p&gt;Terminal output:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git pull&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Received disconnect from &amp;lt;ipAddress&amp;gt; port &amp;lt;portId&amp;gt;: Too many authentication failures&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So, we have a connection to the server 😁, but it won&amp;rsquo;t let us in 😨. At least it tells us why: it claims we&amp;rsquo;ve tried too many keys at its front door.&lt;/p&gt;&#10;&lt;h3 id="checking-the-server-login-limit"&gt;Checking the server login limit&lt;/h3&gt;&#10;&lt;p&gt;This error occurs when the server&amp;rsquo;s limit for login attempts is exceeded. For the &lt;code&gt;sshd&lt;/code&gt; service, the default value is, for example, &lt;a href="https://www.baeldung.com/linux/too-many-authentication-failures-error" target="_blank" rel="noopener noreferrer" class="external-link"&gt;MaxAuthTries = 6&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. If you have saved many keys for multiple connections - or, like me, have recently started using &lt;a href="https://blog.schallbert.de/en/hardware-security-keys-yubikey/"&gt;Hardware Security Keys&lt;/a&gt; and set up backup keys (effectively doubling the key count) - this limit is quickly reached.&lt;/p&gt;&#10;&lt;p&gt;The tricky thing about this error is that it only appears with specific services &amp;ldquo;further down&amp;rdquo; in the agent&amp;rsquo;s keychain: after all, five keys are allowed to fail before the limit is hit. Furthermore, having the keys correctly configured in &lt;code&gt;~/.ssh/config&lt;/code&gt; doesn&amp;rsquo;t protect you; the agent always attempts to use all available keys unless explicitly told otherwise.&lt;/p&gt;&#10;&lt;p&gt;We can prevent this by using &lt;code&gt;IdentitiesOnly yes&lt;/code&gt;. This explicitly instructs the agent to use only the identities (i.e., key files or username/password) specified for that particular host.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# /.ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hostname git.schallbert.de&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Port &lt;span style="color:#ae81ff"&gt;222&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User &amp;lt;user&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PreferredAuthentications publickey&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IdentityFile &amp;lt;path_to_private_key1&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Identityfile &amp;lt;path_to_private_key2&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;IdentitiesOnly yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="checking-the-git-config"&gt;Checking the Git config&lt;/h3&gt;&#10;&lt;p&gt;You can also encounter this error if the repository&amp;rsquo;s Git configuration (located at &lt;code&gt;&amp;lt;reponame&amp;gt;/.git/config&lt;/code&gt;) does not match the server and repository details defined in your SSH configuration (&lt;code&gt;~/.ssh/config&lt;/code&gt;). If you change the SSH configuration, all repository configurations maintained locally must also be updated.&lt;/p&gt;&#10;&lt;p&gt;Example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# &amp;lt;reponame&amp;gt;/.git/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# [...]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;remote &lt;span style="color:#e6db74"&gt;&amp;#34;origin&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; ssh://git@git.schallbert.de:222/schallbert/&amp;lt;reponame&amp;gt;.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;fetch &lt;span style="color:#f92672"&gt;=&lt;/span&gt; +refs/heads/*:refs/remotes/origin/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;branch &lt;span style="color:#e6db74"&gt;&amp;#34;main&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;remote &lt;span style="color:#f92672"&gt;=&lt;/span&gt; origin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;merge &lt;span style="color:#f92672"&gt;=&lt;/span&gt; refs/heads/main&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;vscode-merge-base &lt;span style="color:#f92672"&gt;=&lt;/span&gt; origin/main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In this case, the &lt;code&gt;gitserver&lt;/code&gt; configuration is not used at all; instead, the connection is established directly. Consequently, the agent does not receive the &lt;code&gt;IdentitiesOnly yes&lt;/code&gt; flag and cycles through keys until the server rejects the connection. Solution:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;remote &lt;span style="color:#e6db74"&gt;&amp;#34;origin&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; git@gitserver:schallbert/&amp;lt;reponame&amp;gt;.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;fetch &lt;span style="color:#f92672"&gt;=&lt;/span&gt; +refs/heads/*:refs/remotes/origin/*&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="problem-permission-denied-publickeypassword"&gt;Problem: Permission Denied (publickey,password)&lt;/h2&gt;&#10;&lt;p&gt;Console output:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh -T git@gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git@git.schallbert.de: Permission denied &lt;span style="color:#f92672"&gt;(&lt;/span&gt;publickey,password&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Unfortunately, this problem can have many different causes. GitHub&amp;rsquo;s &lt;a href="https://docs.github.com/en/authentication/troubleshooting-ssh/error-permission-denied-publickey" target="_blank" rel="noopener noreferrer" class="external-link"&gt;guide&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; addresses a few of them, including:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Using &lt;code&gt;sudo&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;Wrong server&lt;/li&gt;&#10;&lt;li&gt;Not using the &lt;code&gt;git&lt;/code&gt; user&lt;/li&gt;&#10;&lt;li&gt;Using the wrong key (client)&lt;/li&gt;&#10;&lt;li&gt;Key not registered (server)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The output for an incorrect username looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ ssh -T mit@gitserver&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mit@git.schallbert.de: Permission denied &lt;span style="color:#f92672"&gt;(&lt;/span&gt;publickey&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Another cause can be a mismatch between the &lt;code&gt;~/.ssh/config&lt;/code&gt; configuration file and the remote address stored in the &lt;em&gt;git client&lt;/em&gt;. If they do not match, any existing configuration will not be used at all.&lt;/p&gt;&#10;&lt;p&gt;You can verify this using &lt;code&gt;git remote -v&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git remote -v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;ssh://git@git.schallbert.de:222/schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;fetch&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;ssh://git@git.schallbert.de:222/schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;push&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The correct approach, using the improved configuration file (with ports explicitly set and identity files defined and restricted to the specified ones), looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Update link to server&amp;#39;s git repository, use host alias from config file&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git remote update origin git@gitserver:schallbert/blog.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Now check if the update was effective&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;article&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git remote -v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;git@gitserver:schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;fetch&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;origin&#9;git@gitserver:schallbert/blog.git &lt;span style="color:#f92672"&gt;(&lt;/span&gt;push&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="problem-agent-refused-operation"&gt;Problem: Agent Refused Operation&lt;/h3&gt;&#10;&lt;p&gt;Console output:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;➜ blog git:&lt;span style="color:#f92672"&gt;(&lt;/span&gt;local-setup&lt;span style="color:#f92672"&gt;)&lt;/span&gt; ✗ git push --set-upstream origin local-setup&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sign_and_send_pubkey: signing failed &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ED25519-SK &amp;lt;path-to-private-key&amp;gt; from agent: agent refused operation&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I have only encountered this error since I started using &lt;a href="https://blog.schallbert.de/en/hardware-security-key-add-ssh/"&gt;hardware security keys for SSH&lt;/a&gt;. As I see it, there are two possible causes. Fortunately, both are easy to fix:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The agent does not recognize the key. The &lt;code&gt;ssh-add&lt;/code&gt; command resolves this. Verify using &lt;code&gt;ssh-add -l | grep &amp;quot;&amp;lt;your-key's-comment&amp;gt;&amp;quot;&lt;/code&gt;&lt;/li&gt;&#10;&lt;li&gt;The requested key is missing. This can happen with HSKs and HSMs if they are not plugged in, not positioned correctly on the NFC reader, or if the HSM is unavailable on the network. It can also occur if the &lt;code&gt;~/.ssh/config&lt;/code&gt; file was edited manually and the key has since been renamed or deleted.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item><item><title>Hardware Security Key: add SSH</title><link>https://blog.schallbert.de/en/hardware-security-key-add-ssh/</link><pubDate>Sun, 03 May 2026</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/hardware-security-key-add-ssh/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-ssh-randomart_thumb.avif"&#10; class="post-cover"&#10; alt="Image: An SSH public key randomart image as visual fingerprint for humans to quickly see if public keys match"&#10; title="Hardware Security Key: add SSH" /&gt;&#10;&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&#10;&lt;p&gt;In my post about &lt;a href="https://blog.schallbert.de/en/hardware-security-keys-yubikey/"&gt;hardware security keys&lt;/a&gt;, I wrote that I want to secure as many of my connections as possible without passwords provided by my new HSK. I use &lt;em&gt;SSH&lt;/em&gt; connections for various services and purposes: I access my &lt;a href="https://en.wikipedia.org/wiki/Virtual_private_server" target="_blank" rel="noopener noreferrer" class="external-link"&gt;VPS&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, use services such as GitHub, Gitea and my own &lt;a href="https://blog.schallbert.de/en/projects/move-blog-to-own-server/"&gt;Gitea instance&lt;/a&gt; and run backups on the local network via &lt;a href="https://linuxize.com/post/how-to-use-scp-command-to-securely-transfer-files/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;code&gt;scp&lt;/code&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, which uses the SSH protocol.&lt;/p&gt;&#10;&lt;h2 id="setting-up-ssh-access-for-hardware-security-keys"&gt;Setting up SSH access for hardware security keys&lt;/h2&gt;&#10;&lt;p&gt;HSKs such as my Yubikey can store private keys for SecureShell access. This means you no longer need to store them locally on every device (although this remains configurable; see below). Furthermore, they are better protected against unauthorised access than if stored on a computer.&lt;/p&gt;&#10;&lt;p&gt;So I&amp;rsquo;m trying to switch my GitHub SSH access to HSK and am following &lt;a href="https://www.yubico.com/blog/github-now-supports-ssh-security-keys/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Yubico&amp;rsquo;s guide to passwordless authentication for GitHub&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. The procedure for generating FIDO-compliant SSH keys is the same across different key manufacturers. I&amp;rsquo;m also linking to the &lt;a href="https://docs.nitrokey.com/nitrokeys/features/fido2/ssh" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Nitrokey guide&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; for reference.&lt;/p&gt;&#10;&lt;h2 id="what-doesnt-work"&gt;What doesn&amp;rsquo;t work&lt;/h2&gt;&#10;&lt;p&gt;However, even with Yubico&amp;rsquo;s own recommendations, I am unable to achieve the desired result on my operating system and with the standard installation of &lt;code&gt;openssh&lt;/code&gt;. The command assembled from the guide is:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-keygen -t ed25519-sk -O resident -O verify-required -O application&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ssh:github -O user&lt;span style="color:#f92672"&gt;=&lt;/span&gt;schallbert -C &lt;span style="color:#e6db74"&gt;&amp;#39;schallbert@github.com&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Once generated, we must store the public key (file extension &lt;code&gt;.pub&lt;/code&gt;) in the web application of the respective service under the SSH Access menu item.&lt;/p&gt;&#10;&lt;h3 id="meaning-of-the-command-and-the-most-important-options"&gt;Meaning of the command and the most important options&lt;/h3&gt;&#10;&lt;p&gt;This command&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; tells &lt;a href="https://www.man7.org/linux/man-pages/man1/ssh-keygen.1.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;ssh-keygen&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; to generate a key of type &lt;code&gt;-t ed25519-sk&lt;/code&gt; for me. The suffix &lt;code&gt;-sk&lt;/code&gt; indicates that the key should be stored on a hardware security key - in my case, the Yubikey.&lt;/p&gt;&#10;&lt;p&gt;To understand the &lt;code&gt;-O resident&lt;/code&gt; flag, we need to know that the FIDO authenticator key, generated as with &lt;code&gt;-sk&lt;/code&gt;, consists of two parts: a &lt;code&gt;key handle&lt;/code&gt; part, which remains on the computer&amp;rsquo;s hard drive, and the private key itself, which &lt;a href="https://www.man7.org/linux/man-pages/man1/ssh-keygen.1.html#FIDO_AUTHENTICATOR" target="_blank" rel="noopener noreferrer" class="external-link"&gt;exists only on the HSM&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Only together can they correctly respond to authentication requests. The flag itself, however, instructs the program to write both parts of the key to the Yubikey. This makes it easier to use multiple devices with the same key: the &lt;code&gt;key handle&lt;/code&gt; part can be read from the Yubikey and stored on other end devices. On the other hand, however, simply losing the Yubikey is now enough to grant potential attackers SSH access.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-f filename&lt;/code&gt; specifies the names for the key files. This is particularly useful when you need to manage multiple SSH connections or HSMs. I&amp;rsquo;ve created a naming convention to avoid confusion. It&amp;rsquo;s based on the syntax used to read keys from an HSM (&lt;code&gt;ssh-keygen -K&lt;/code&gt;).&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-f &amp;lt;keyType&amp;gt;_&amp;lt;protocol&amp;gt;:&amp;lt;service&amp;gt;_&amp;lt;user&amp;gt;_&amp;lt;device&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As a concrete example, my filename here would be as follows:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-f ed25519-sk_ssh:github_schallbert_yubikey-main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;⚠️ Note when using Windows: The colon &lt;code&gt;:&lt;/code&gt; is not allowed as a character in filenames. It is replaced by an underscore &lt;code&gt;_&lt;/code&gt;, which must also be reflected in the &lt;code&gt;config&lt;/code&gt; file (&lt;code&gt;Ctrl+H&lt;/code&gt; -&amp;gt; replace &lt;code&gt;:&lt;/code&gt; with &lt;code&gt;_&lt;/code&gt;)&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-C schallbert@github.com&lt;/code&gt; adds a comment to the key file itself, such as a contact address. This gives the key a label, which makes it easier to manage when dealing with multiple keys.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O application=ssh:github&lt;/code&gt; is simply for neat listing in the Authenticator and ensures that I cannot confuse one key with another. Otherwise, it would simply say &lt;code&gt;ssh:keygen&lt;/code&gt; and I would no longer be able to see which services I have stored keys for. Also helpful to discriminate keys when using the &lt;code&gt;-resident&lt;/code&gt; option in congunction with key extraction &lt;code&gt;ssh-keygen -K&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O user=schallbert&lt;/code&gt; has no practical use for me, as SSH is not interested in this flag and, contrary to my expectations, the Yubikey Authenticator does not display the username.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O no-touch-required&lt;/code&gt; prevents the hardware security key from having to be touched every time the key is used. Useful for very frequent use, though it compromises security due to the possibility of automation.&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;-O verify-required&lt;/code&gt; specifies that the HSM&amp;rsquo;s PIN must be entered before the key can be used. Although intended as an additional security measure, this causes problems for me in my shell. Specifically, the PIN is requested once, but the &lt;code&gt;ssh-agent&lt;/code&gt; does not generate a prompt in my console every time. As soon as I try to connect via SSH in another session, I receive an error message: &lt;code&gt;agent refused operation&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh -T git@github.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sign_and_send_pubkey: signing failed &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; ED25519-SK &lt;span style="color:#e6db74"&gt;&amp;#34;schallbert@github.com&amp;#34;&lt;/span&gt; from agent: agent refused operation&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;root@opnsense: Permission denied &lt;span style="color:#f92672"&gt;(&lt;/span&gt;publickey&lt;span style="color:#f92672"&gt;)&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="what-was-the-problem"&gt;What was the problem?&lt;/h2&gt;&#10;&lt;p&gt;After a long, frustrating troubleshooting session (What am I doing wrong? Does it work with the secondary key? Why does it work the first time but not afterwards? What if I regenerate the keys? What happens on other computers? What does &amp;lsquo;AI&amp;rsquo; say about this? - The latter certainly took an hour and left me completely stuck), I try a different command. That works straight away.&lt;/p&gt;&#10;&lt;h2 id="what-does-work-however"&gt;What does work, however&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-keygen -t ed25519-sk -O application&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ssh:github -C &lt;span style="color:#e6db74"&gt;&amp;#39;schallbert@github.com&amp;#39;&lt;/span&gt; -f ed25519-sk_ssh:github_schallbert_yubikey-main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If I omit &lt;code&gt;verify-required&lt;/code&gt;, I can log in successfully and the SSH agent runs fine.&lt;/p&gt;&#10;&lt;h3 id="what-verify-required-requires"&gt;What &lt;code&gt;verify-required&lt;/code&gt; requires&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://superuser.com/questions/1804740/yubikey-ssh-authentication-fails-with-signing-failed-for-ecdsa-sk" target="_blank" rel="noopener noreferrer" class="external-link"&gt;This post&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; sheds some light on my issue. The terminal session I use to create the SSH key pair temporarily stores the PIN entry enforced by the &lt;code&gt;verify-required&lt;/code&gt; flag. That is why the login works initially. If I log in later or in a different window, as I did above to check &lt;code&gt;git pull&lt;/code&gt; in one of my repos, for example, the SSH agent does not ask for my PIN.&lt;/p&gt;&#10;&lt;p&gt;This is likely because the SSH agent itself cannot display prompts to the requesting programme. A submodule of OpenSSH provides a solution: &lt;a href="https://packages.fedoraproject.org/pkgs/openssh/openssh-askpass/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;openssh-askpass&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; . This can be passed to the agent, which then transmits the PIN prompt to the user via &lt;em&gt;askpass&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h3 id="installing-ssh-askpass"&gt;Installing ssh-askpass&lt;/h3&gt;&#10;&lt;p&gt;My operating system did not have &lt;em&gt;ssh-askpass&lt;/em&gt; installed by default, so I&amp;rsquo;ll install it here. It uses the package manager &lt;code&gt;DNF&lt;/code&gt; (&amp;lsquo;Dandified YUM&amp;rsquo;) instead of &lt;em&gt;apt&lt;/em&gt; or other common managers. In my opinion, the naming convention isn&amp;rsquo;t 100% consistent: whilst I address the ssh programme in the terminal as &lt;em&gt;ssh-agent&lt;/em&gt;, I have to type &lt;em&gt;openssh-agent&lt;/em&gt; for the installation.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# verify-required needs openssh subpackage through Fedora&amp;#39;s package manager&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# sudo dnf install openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Installing:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Complete!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now I want to tell the agent that it can use &lt;em&gt;ssh-askpass&lt;/em&gt; to request the PIN. To do this, I need to know the path to the submodule.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# which openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/which: no openssh-askpass &lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Strange, I installed it above! Let&amp;rsquo;s have a look what the package manager has to say.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# dnf repoquery --list openssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;[&lt;/span&gt;...&lt;span style="color:#f92672"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/libexec/openssh/ssh-askpass&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ah, openssh hides in &lt;code&gt;libexec&lt;/code&gt;. Well, then I can link &lt;code&gt;ssh-agent&lt;/code&gt; to &lt;code&gt;ssh-askpass&lt;/code&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# eval &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ssh-agent -s; SSH_ASKPASS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/usr/libexec/openssh/ssh-askpass&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Agent pid &lt;span style="color:#ae81ff"&gt;16747&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-ssh-askpass-prompt.avif" alt="Image: Testing the ssh connection to Github with ssh -T and getting prompted for HSK device pin"&gt;&lt;/figure&gt;&#10;I check the connection to GitHub and, as expected, a dialogue box appears asking for my PIN. I then need to tap the HSK, after which I receive a success message. To ensure that this all works in a new terminal and after restarting the computer, I add a reference to the key in my configuration file.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ~/.ssh/config&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Host github&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname github.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; PreferredAuthentications publickey&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentityFile ~/.ssh/ed25519-sk_ssh:github_schallbert_yubikey-main&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To be on the safe side, restart the agent and create new terminal sessions. If any login details were cached, this will reset them. When done, validate SSH connectivity.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; schallbert@machine:~# eval &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ssh-agent -k&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; eval &lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ssh-agent -s&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="annoying-gnome-pop-up-allow-inhibiting-shortcuts"&gt;Annoying: Gnome pop-up &amp;lsquo;Allow inhibiting shortcuts&amp;rsquo;&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-gnome-inhibit-shortcuts-prompt.avif" alt="Image: Gnome pop-up &amp;#39;Allow inhibiting shortcuts"&gt;&lt;/figure&gt;&#10;The only downside is that I now get an extra pop-up when connecting via SSH. This is triggered by the &lt;em&gt;gnome&lt;/em&gt; graphical user interface. Unfortunately, I can&amp;rsquo;t find an option to save the relevant setting permanently. As a result, it appears every time I connect with &lt;code&gt;verify-required&lt;/code&gt;. With Gnome, the problem is already &lt;a href="https://gitlab.gnome.org/GNOME/gnome-connections/-/work_items/85" target="_blank" rel="noopener noreferrer" class="external-link"&gt;known to affect other applications&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, but apparently has not yet been resolved.&lt;/p&gt;&#10;&lt;h2 id="transferring-to-windows-systems"&gt;Transferring to Windows systems&lt;/h2&gt;&#10;&lt;p&gt;I generated my keys using the &lt;code&gt;-O resident&lt;/code&gt; option. It makes sense to test the extraction of the &lt;code&gt;key-handle&lt;/code&gt; on a completely different system: &lt;em&gt;Windows&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;I open the command line and type&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@windows-machine:~# ssh-keygen -K&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Enter PIN &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; authenticator:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;You may need to touch your authenticator to authorize key download.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Provider &lt;span style="color:#e6db74"&gt;&amp;#34;internal&amp;#34;&lt;/span&gt; returned failure -1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Unable to load resident keys: invalid format&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Invalid format? It&amp;rsquo;s often mentioned online that the keys sometimes need a &lt;a href="https://stackoverflow.com/questions/47859437/windows-10-openssh-key-invalid-format#68593742" target="_blank" rel="noopener noreferrer" class="external-link"&gt;line feed character&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; at the end to be recognized correctly. However, I cannot modify them without downloading them from HSK. So I manually transfer the public key and key handle to the computer and try out the &lt;code&gt;git pull&lt;/code&gt; command as a test. And look: it works!&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-windows-fido-key-ssh-pin-prompt.avif" alt="Image: FIDO ssh connection test on a Windows machine: OS prompts for PIN entry. It works!"&gt;&lt;/figure&gt;&#10;&lt;p&gt;So if you want to use the keys with different operating system families, the &lt;code&gt;-resident&lt;/code&gt; option doesn&amp;rsquo;t seem to be of any use. The command &lt;code&gt;ssh-keygen -K&lt;/code&gt; does not allow granularity: It will load all resident keys from the HSK at once.&lt;/p&gt;&#10;&lt;h2 id="vps-be-careful-when-replacing-keys"&gt;VPS: Be careful when replacing keys&lt;/h2&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-05-03-hetzner-key-add-irrelevant-for-existing-servers.avif" alt="Image: Hetzner informing me that new SSH keys will not be applied to existing resources (VPS). Important to know!"&gt;&lt;/figure&gt;&#10;Another tip from personal experience: If you&amp;rsquo;ve already stored SSH keys with a service provider that hosts live-resources itself (in my case, a server as a VPS), new SSH keys are usually not passed on to that resource via the web frontend.&lt;/p&gt;&#10;&lt;p&gt;Consequently, the “old” keys are still needed, and the new ones aren&amp;rsquo;t used, even though they&amp;rsquo;re stored in the web app!&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;The new keys must be manually added to the destination.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;I follow the &lt;a href="https://community.hetzner.com/tutorials/howto-ssh-key/de#step-4---store-ssh-key-on-server" target="_blank" rel="noopener noreferrer" class="external-link"&gt;instructions from my host Hetzner&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; using the &lt;code&gt;ssh-copy-id&lt;/code&gt; command.&lt;/p&gt;&#10;&lt;h3 id="manually-store-new-keys-on-the-vps"&gt;Manually store new keys on the VPS&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-copy-id -i ./ed25519-sk_ssh:server_schallbert_yubikey-main.pub server &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, &lt;code&gt;server&lt;/code&gt; is the SSH alias for my VPS. The connection is established using the familiar “old” key, and the “new” one is saved. But something isn&amp;rsquo;t working as expected:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt;, to filter out any that are already installed&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/usr/bin/ssh-copy-id: WARNING: All keys were skipped because they already exist on the remote system.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;&#9;&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; you think this is a mistake, you may want to use -f option&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="was-the-key-transferred-successfully"&gt;Was the key transferred successfully?&lt;/h3&gt;&#10;&lt;p&gt;So I log back into my server and check whether the new key actually appears in the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file. It doesn&amp;rsquo;t. So I run the command above again, this time with the &lt;code&gt;-f (Force)&lt;/code&gt; option enabled. And sure enough, the transfer works!&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;schallbert@machine:~# ssh-copy-id -f -i ./ed25519-sk_ssh:server_schallbert_yubikey-main.pub server &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Number of key&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt; added: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Now try logging into the machine, with: “ssh -i &amp;lt;identityFile&amp;gt; &lt;span style="color:#e6db74"&gt;&amp;#39;server&amp;#39;&lt;/span&gt;”&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;and check to make sure that only the key&lt;span style="color:#f92672"&gt;(&lt;/span&gt;s&lt;span style="color:#f92672"&gt;)&lt;/span&gt; you wanted were added.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To test it as recommended, log in with the new key and&amp;hellip; It works! Only now can the old keys be safely deleted.&lt;/p&gt;&#10;&lt;h3 id="how-to-avoid-ssh-copy-id"&gt;How to avoid ssh-copy-id&lt;/h3&gt;&#10;&lt;p&gt;If you want to transmit the public key without using additional tools, you can copy the contents of the public key file &lt;code&gt;&amp;lt;key-filename&amp;gt;.pub&lt;/code&gt; directly into a new line of &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt;. You just need an open SSH connection to your target.&lt;/p&gt;&#10;&lt;h2 id="thoughts-on-convenience-and-usability"&gt;Thoughts on convenience and usability&lt;/h2&gt;&#10;&lt;p&gt;When I look at my SSH connections, they broadly fall into two categories:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Admin access&lt;/strong&gt; (VPS, NAS, Raspberry Pi) and signatures&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Protocol access&lt;/strong&gt; (Git etc.)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;In my view, the former require a higher level of security. Furthermore, I don&amp;rsquo;t use them as frequently as version control systems. I therefore recommend &lt;code&gt;verify-required&lt;/code&gt; for the first category and would omit this for the second. I would take the same approach with the &lt;code&gt;no-touch-required&lt;/code&gt; option: if a commit is to be uploaded every few minutes, constantly having to touch the key is rather annoying.&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;All options are displayed when you open the manual using &lt;code&gt;man ssh-keygen&lt;/code&gt;.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item><item><title>Hardware Security Keys</title><link>https://blog.schallbert.de/en/hardware-security-keys-yubikey/</link><pubDate>Sat, 25 Apr 2026</pubDate><author>Schallbert</author><guid>https://blog.schallbert.de/en/hardware-security-keys-yubikey/</guid><description type="html">&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-yubikey_thumb.avif"&#10; class="post-cover"&#10; alt="Image: A brand-new hardware security key in its package on a table. The image has been altered; its colors are value-inverted."&#10; title="Hardware Security Keys" /&gt;&#10;&lt;h2 id="definitions-tpm-and-hsk"&gt;Definitions: TPM and HSK&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: center"&gt;Acronym&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: center"&gt;Term&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: center"&gt;Brief description&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;TPM&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Trusted Platform Module&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Soldered securely. Protects a device&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;HSK&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Hardware Security Key&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: center"&gt;Mobile. Protects accounts, certificates, private keys&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The &lt;a href="https://de.wikipedia.org/wiki/Trusted_Platform_Module" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Trusted Platform Module&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; is a security chip that securely stores cryptographic keys and provides functions such as Secure Boot, BitLocker encryption and device identity. It is permanently soldered onto the motherboard of a computer, smartphone, etc.&lt;/p&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-this-is-a-yubikey.avif" alt="Image: A Hardware Security Key with USB-C connector called YubiKey5 NFC."&gt;&lt;/figure&gt;&#10;A &lt;a href="https://en.wikipedia.org/wiki/Hardware_security_module" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Hardware Security Key &lt;em&gt;HSK&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, on the other hand, is usually portable and can be connected to various devices. It can not only store keys, but often also perform cryptographic operations (key generation, signing, encryption). HSKs are designed to be highly secure and tamper-proof. They can be externally certified or audited, meaning that even highly sensitive processes such as payment transactions can be carried out.&lt;/p&gt;&#10;&lt;h2 id="what-happened-so-far"&gt;What happened so far&lt;/h2&gt;&#10;&lt;p&gt;I listened to a podcast from the Bundesamt für Sicherheit in der Informationstechnik (BSI) on the topic of &lt;a href="https://www.bsi.bund.de/SharedDocs/Audio/DE/BSI/Update_verfuegbar_Folge40_2024_02_29.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&amp;lsquo;Passkeys instead of passwords&amp;rsquo;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Following the recommendations, I bought a Hardware Security Key (HSK) so that I can use &lt;a href="https://en.wikipedia.org/wiki/WebAuthn" target="_blank" rel="noopener noreferrer" class="external-link"&gt;passkeys&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; for my services in future. For a long time now, I have been unhappy with my fragmented secret management system consisting of notes, browser plugins, offline password managers such as &lt;a href="https://en.wikipedia.org/wiki/KeePass" target="_blank" rel="noopener noreferrer" class="external-link"&gt;KeePass&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Secure_Shell" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;SSH&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; credentials stored on my hard drive, backup keys and &lt;a href="https://askleo.com/how-should-i-keep-recovery-codes-secure/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;emergency codes&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; from various providers.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d finally like to bring some order to the situation without tying myself so tightly to a commercial provider that I become dependent on them.&lt;/p&gt;&#10;&lt;h2 id="objectives"&gt;Objectives&lt;/h2&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;No longer having to remember passwords or login details&lt;/li&gt;&#10;&lt;li&gt;Setting up a system for managing passwords that is as simple as possible, yet secure&lt;/li&gt;&#10;&lt;li&gt;Bringing order to the jumble of accounts&lt;/li&gt;&#10;&lt;li&gt;Organising my digital legacy&lt;/li&gt;&#10;&lt;li&gt;Enabling others to do the same&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Welche Erwartung ich jetzt schon einmal nehmen kann: Ganz ohne Passwörter oder Pins geht es auch mit dem HSK nicht. Ein Yubikey ist kein Passwortmanager. Er kann zwar Zertifikate, Passkeys und Secrets für OTPs halten, gibt diese Geheimnisse dann aber nicht mehr preis. Mit seiner Hilfe kann man signieren (Authentizität beweisen), Verschlüsseln und abgeleitete Schlüssel erstellen.&lt;/p&gt;&#10;&lt;p&gt;In fact, password management can be centralised and simplified using the Yubikey. Popular password managers support passkeys meaning a master password is no longer required.&lt;/p&gt;&#10;&lt;h3 id="yubikey"&gt;Yubikey&lt;/h3&gt;&#10;&lt;p&gt;&lt;figure class="media-frame media-frame--right"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-yubico-authenticator.avif" alt="Image: Screenshot of Yubico Authenticator&amp;#39;s menu options, showing &amp;#39;Home&amp;#39;, &amp;#39;Accounts&amp;#39;, &amp;#39;Passkeys&amp;#39;, &amp;#39;Certificates&amp;#39;, and &amp;#39;Slots&amp;#39;"&gt;&lt;/figure&gt;&#10;As an HSK, I use two &lt;a href="https://www.yubico.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Yubikey&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; key fobs. One to carry with me at all times and one as a spare, which is kept in a safe place. There are other providers of mobile Hardware Security Keys, such as &lt;a href="https://www.nitrokey.com/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Nitrokey&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. I wasn&amp;rsquo;t aware of them before writing this article. That&amp;rsquo;s a shame, as their product is open-source and therefore an ideal candidate for testing on my blog 😥.&lt;/p&gt;&#10;&lt;h3 id="yubico-authenticator"&gt;Yubico Authenticator&lt;/h3&gt;&#10;&lt;p&gt;The Yubikey Authenticator GUI programme is useful for setting up the device, assigning functions to the &lt;em&gt;Slot&lt;/em&gt; key (e.g. confirmation or password entry via keyboard simulation) and if you use &lt;a href="https://www.bsi.bund.de/DE/Themen/Verbraucherinnen-und-Verbraucher/Informationen-und-Empfehlungen/Cyber-Sicherheitsempfehlungen/Accountschutz/Zwei-Faktor-Authentisierung/zwei-faktor-authentisierung_node.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;2FA&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;-Einmalkennwörter z.B. als &lt;a href="https://en.wikipedia.org/wiki/Time-based_one-time_password" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;TOTP&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Furthermore, my Yubikey supports &lt;a href="https://en.wikipedia.org/wiki/Public_key_infrastructure" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;PKI&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, meaning that &lt;a href="https://letsencrypt.org/certificates/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;SSL&lt;/em&gt; certificates&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; and private keys can be stored securely.&lt;/p&gt;&#10;&lt;h2 id="concept-of-the-hsk"&gt;Concept of the HSK&lt;/h2&gt;&#10;&lt;p&gt;Part of the concept is that private keys are either generated directly on the hardware key or - without being permanently stored locally - are created on an end device but stored on the Yubikey. These remain on the key and cannot be exported. It supports common asymmetric methods such as &lt;em&gt;RSA&lt;/em&gt; and &lt;em&gt;ECDH&lt;/em&gt;. For further details, see this &lt;a href="https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Publikationen/TechnischeRichtlinien/TR02102/BSI-TR-02102.pdf?__blob=publicationFile&amp;amp;v=15" target="_blank" rel="noopener noreferrer" class="external-link"&gt;BSI publication on cryptographic methods&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Furthermore, the key will be kept offline most of the time, i.e. without a power supply. Thus it is virtually impossible to compromise. An additional PIN as a protective measure in the event of key loss is a further security factor.&lt;/p&gt;&#10;&lt;h3 id="spare-key"&gt;Spare key&lt;/h3&gt;&#10;&lt;p&gt;Nevertheless, you should make a note of which access points you protect using which method, so that the correct accounts can be locked in the event of loss. A &amp;lsquo;&lt;a href="https://support.yubico.com/s/article/How-to-register-your-spare-key" target="_blank" rel="noopener noreferrer" class="external-link"&gt;spare key&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&amp;rsquo; should be available and set up, which is almost as time-consuming as configuring the main key.&lt;/p&gt;&#10;&lt;h3 id="nfc"&gt;NFC&lt;/h3&gt;&#10;&lt;p&gt;I&amp;rsquo;d hoped that my key&amp;rsquo;s &lt;a href="https://en.wikipedia.org/wiki/Near-field_communication" target="_blank" rel="noopener noreferrer" class="external-link"&gt;&lt;em&gt;NFC&lt;/em&gt;&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; support would make logging in even quicker and easier. However, two things bother me here: I simply cannot get my laptop to recognise the Yubikey via NFC. And on my mobile, NFC recognition works at best flakily. When I hold the key up to it, the app launches and asks me to enter the PIN. If I do so, I have to remove the key and hold it up to the sensor again (&amp;lsquo;Do not move the key!&amp;rsquo;). This doesn&amp;rsquo;t work every time by a long shot and seems rather prone to glitches overall. A pity.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-nfc-device-manager.avif" alt="Image: NFC shown in device manager of my Windows laptop: Drivers installed but not detecting my Yubikey"&gt;&lt;/figure&gt;&#10;&lt;h2 id="what-i-find-myself-missing-when-using-the-yubikey"&gt;What I find myself missing when using the Yubikey&lt;/h2&gt;&#10;&lt;p&gt;Overall, I do miss a few convenience features in my day-to-day use of the Yubikey.&#10;When I click on the desired keys or accounts, nothing happens. And it seems I can&amp;rsquo;t configure anything there either.&lt;/p&gt;&#10;&lt;p&gt;When using &lt;strong&gt;passkeys&lt;/strong&gt;, I&amp;rsquo;d like to see a shortcut. Clicking on the relevant key in the authenticator should open the corresponding app or browser window with the target website. Since I&amp;rsquo;ve already unlocked the HSK with the PIN at this point, the login process can even take place fully automatically once the key is tapped.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;d like to see something similar when managing &lt;strong&gt;accounts&lt;/strong&gt;. Why is the module called &amp;lsquo;Accounts&amp;rsquo; if I can&amp;rsquo;t access them directly? Here, too, I have to open the relevant web apps etc. myself, have the password manager ready, and then copy the 2FA codes from the authenticator. There must be an easier way.&lt;/p&gt;&#10;&lt;h3 id="touch-password-pin-puk-management-key-passphrase"&gt;Touch, password, PIN, PUK, management key, passphrase&lt;/h3&gt;&#10;&lt;p&gt;Depending on the module, multi-factor authentication is usually required even when using the Yubikey. The simplest method is presence detection via a touch of the dongle. This ensures that a person is actively operating the HSK and cannot, for example, be logged in automatically. &lt;em&gt;Touch&lt;/em&gt; can be used across modules; for detailed configuration, see &lt;a href="https://blog.schallbert.de/en/hardware-security-key-add-ssh/"&gt;my article on SSH&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;The &lt;em&gt;Accounts&lt;/em&gt; module can optionally be secured with a password, whilst the &lt;em&gt;Passkeys&lt;/em&gt; module is secured via &lt;em&gt;PIN&lt;/em&gt;. Some providers even require a PIN to be set up as a second factor.&lt;/p&gt;&#10;&lt;p&gt;The &lt;em&gt;Certificates&lt;/em&gt; module must be protected against unauthorised access using &lt;a href="https://docs.yubico.com/yesdk/users-manual/application-piv/pin-puk-mgmt-key.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PIN, PUK and management key&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. Private keys or certificates can optionally be protected with a &lt;a href="https://www.ssh.com/academy/ssh/passphrase" target="_blank" rel="noopener noreferrer" class="external-link"&gt;passphrase&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;, which locks the keys themselves (and, in my view, makes no sense for the Yubikey, as the private keys cannot be read anyway).&lt;/p&gt;&#10;&lt;h2 id="feature-set-and-cryptographic-landscape"&gt;Feature set and cryptographic landscape&lt;/h2&gt;&#10;&lt;p&gt;Since the HSK promises such a significant security boost, I naturally want to make use of as many of its capabilities as possible. I&amp;rsquo;ve been playing around with the various modules for a few weeks now and have found that you really need to get your head around them and learn a lot to be able to use them all effectively. At least, once you&amp;rsquo;ve done so, you&amp;rsquo;ll have a rough idea of what the modules do and gain a good overview of internet security, encryption methods and protocols.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;“Accounts” and “Passkeys” are easy to set up for input forms (platforms, websites, shops) and apps (email clients, password managers, messengers): Highly recommended! - schallbert&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Yubikey module&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Description&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Accounts&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Management of accounts/logins, supports TOTP&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Passkeys&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Passwordless login procedure via &lt;a href="https://bitmi.de/it-sicherheitsblog-was-ist-eigentlich-fido2/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;FIDO/WebAuthn&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (public key credential)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Certificates&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Storage and use of certificates; supports &lt;a href="https://www.openpgp.org/" target="_blank" rel="noopener noreferrer" class="external-link"&gt;OpenPGP&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (PGP) and &lt;a href="https://www.bsi.bund.de/SharedDocs/Downloads/DE/BSI/Publikationen/TechnischeRichtlinien/TR02103/BSI-TR-02103.pdf?__blob=publicationFile&amp;amp;v=2" target="_blank" rel="noopener noreferrer" class="external-link"&gt;X.509&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt; (PIV)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Slots&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Two programmable slots for generating static passwords or one-time passwords&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;In my opinion, things get complicated and time-consuming with public/private keys and certificates. Generation and validation procedures depend on the operating system or the programmes available on it, and instructions are sometimes insufficient or contradictory: only for nerds or professionals. - schallbert&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Let&amp;rsquo;s go through the modules.&lt;/p&gt;&#10;&lt;h3 id="two-factor-authentication-yubikey-accounts"&gt;Two-factor authentication: “Yubikey: Accounts”&lt;/h3&gt;&#10;&lt;p&gt;The Yubikey can be used as a generator for time-based one-time passwords. To do this, as with any other authenticator app, you create an account for each service and transfer a security key (secret) to the Yubikey. Once the HSK is connected, one-time codes can now be generated based on this secret.&lt;/p&gt;&#10;&lt;p&gt;As there is no way to export these secrets from the Yubikey, the process must be repeated for secondary keys. However, not every provider supports the use of multiple authenticators. So it may be sensible to keep the secret in the clipboard&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt; and reuse it when configuring the secondary key(s).&lt;/p&gt;&#10;&lt;h3 id="passkeys-yubikey-passkeys"&gt;Passkeys &amp;lsquo;Yubikey: Passkeys&amp;rsquo;&lt;/h3&gt;&#10;&lt;p&gt;The Passkey module on my Yubikey works quite well. Unfortunately, many services&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt; do not yet support passwordless login or only accept Passkeys as a second factor. With other services&lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;, I cannot disable the password, so I see little security benefit, or I am issued with backup codes that are intended to serve as a second factor in an emergency&lt;sup id="fnref:4"&gt;&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref"&gt;4&lt;/a&gt;&lt;/sup&gt;. At least, I can now bypass the password-TOTP process for some services and log in more quickly.&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&amp;lsquo;The biggest advantages of the technology for users are, on the one hand, that in future they will no longer have to create and manage passwords. On the other hand, passkeys are immune to the vast majority of known phishing attacks.&amp;rsquo; - &lt;a href="https://www.bsi.bund.de/DE/Themen/Verbraucherinnen-und-Verbraucher/Informationen-und-Empfehlungen/Cyber-Sicherheitsempfehlungen/Accountschutz/Passkeys/passkeys-anmelden-ohne-passwort_node.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;BSI website, 2025 October&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;You do not need the Authenticator app to configure passkeys. This is done entirely within the browser or the command line. Both must support the handling of passkeys.&lt;/p&gt;&#10;&lt;h3 id="keys--certificates-yubikey-certificates"&gt;Keys &amp;amp; Certificates &amp;lsquo;Yubikey: Certificates&amp;rsquo;&lt;/h3&gt;&#10;&lt;p&gt;To shed some light on this, we first need to understand the difference between a key pair and a certificate in the context of authentication. In both cases, the aim is to verify the client&amp;rsquo;s identity. This may be done with a server (e.g. via SSH), an institution (e.g. a Certificate Authority) or another client (e.g. via &lt;em&gt;PGP&lt;/em&gt;). This allows the destination to confirm that the client attempting to connect is indeed authorised to do so.&lt;/p&gt;&#10;&lt;h3 id="publicprivate-key-pair"&gt;Public/Private Key Pair&lt;/h3&gt;&#10;&lt;p&gt;A key pair alone (i.e. without a certificate) is typically used when the communication destination is controlled by the same party as the client. In such cases, the client can place the public key on the host to establish the connection. This method is used, when setting up SSH connections and for certain APIs, such as Google Search Console.&lt;/p&gt;&#10;&lt;figure class="media-frame media-frame--center"&gt;&#10; &lt;img src="https://blog.schallbert.de/assets/images/posts/2026-04-25-gitea-ssh-keys.avif" alt="Image: Gitea account security settings: SSH public key stored in Gitea Webapp"&gt;&lt;/figure&gt;&#10;&lt;p&gt;The host can send a test message to the cient encrypted with the public key, which can only be converted back into plain text using the corresponding private key. If the client sends the correct plain text back to the host, this proves to the host that the connection partner is authentic.&lt;/p&gt;&#10;&lt;h3 id="certificates--piv"&gt;Certificates / PIV&lt;/h3&gt;&#10;&lt;p&gt;But what if the host is not under the client&amp;rsquo;s control? How can we then ensure that neither the client nor the server is impersonating someone else and has stored a forged public key? This is precisely where certificates are used.&lt;/p&gt;&#10;&lt;p&gt;Put simply, the certificate contains not only the public key but also additional information about the network participant and the desired connection. Now a trusted third party, uninvolved in the communication between client and server, comes into play. In the Private Key Infrastructure PKI, this is known as the Certificate Authority &lt;em&gt;CA&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;The network participant now sends the public key, together with metadata about themselves, to the CA and submits a so-called &lt;em&gt;CSR&lt;/em&gt;, a Certificate Signing Request. The CA then verifies whether the participant&amp;rsquo;s channel actually belongs to them.&lt;/p&gt;&#10;&lt;p&gt;In the case of an email (keyword &lt;a href="https://www.gpg4win.org/doc/en/gpg4win-compendium_8.html" target="_blank" rel="noopener noreferrer" class="external-link"&gt;PGP/GPG&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;), the CA can, for example, send an individual hash link - derived from the requestor&amp;rsquo;s public key - to the specified email address. If the link is clicked, this proves to the CA that the email address actually belongs to the client, and the CA can then issue the certificate, i.e. sign the CSR with its private key.&lt;/p&gt;&#10;&lt;p&gt;For domains (keyword SSL/TLS), the CA can check with the Domain Name Service provider whether the details match the server&amp;rsquo;s request.&lt;/p&gt;&#10;&lt;p&gt;Clients have the signatures of common Certificate Authorities stored (e.g. in the browser). If the certificate is tampered with on the return journey between server and client, its hash value changes and the client displays a certificate warning: &amp;lsquo;invalid certificate&amp;rsquo;.&#10;However, one must now trust the Certificate Authorities&lt;sup id="fnref:5"&gt;&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref"&gt;5&lt;/a&gt;&lt;/sup&gt; 🙃&lt;/p&gt;&#10;&lt;div class="footnotes" role="doc-endnotes"&gt;&#10;&lt;hr&gt;&#10;&lt;ol&gt;&#10;&lt;li id="fn:1"&gt;&#10;&lt;p&gt;The computer should be offline for this. If you want to be extra secure, carry out the pairing on a live operating system. This usually boots from a USB stick and forgets everything after shutdown.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:2"&gt;&#10;&lt;p&gt;E.g. Not at all: Mailbox; 2FA: Discord and Kraken, as of 2026 April. But perhaps I haven&amp;rsquo;t found the right settings?&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:3"&gt;&#10;&lt;p&gt;E.g. Google, GitHub and Hetzner, as of 2026 April.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:4"&gt;&#10;&lt;p&gt;These backup codes must also be stored securely. Ideally offline again, but definitely independently of the HSK.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li id="fn:5"&gt;&#10;&lt;p&gt;There are alternatives to institutions such as CAs; see &lt;a href="https://de.wikipedia.org/wiki/Web_of_Trust" target="_blank" rel="noopener noreferrer" class="external-link"&gt;Web of Trust&lt;span class="external-link-icon" aria-hidden="true"&gt;↗&lt;/span&gt;&lt;/a&gt;. For example, identity verification can also take place peer-to-peer via uninvolved third parties.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/div&gt;&#10;</description></item></channel></rss>