<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Adam Brauns Blog</title>
    <link>https://adambrauns.com/blog</link>
    <description>Latest posts from Adam Brauns</description>
    <language>en-us</language>
    <atom:link href="https://adambrauns.com/rss.xml" rel="self" type="application/rss+xml" />
    <item>
          <title>Setting Up a GPG Master Key with Subkeys</title>
          <link>https://adambrauns.com/blog/gpg-master-key-with-subkeys</link>
          <guid>https://adambrauns.com/blog/gpg-master-key-with-subkeys</guid>
          <pubDate>Tue, 31 Mar 2026 17:00:00 GMT</pubDate>
          <description>A practical walkthrough for creating a GPG key pair with separate subkeys for signing and encryption, so a compromised device never means a compromised identity.</description>
          <content:encoded><![CDATA[<p>GPG is one of those tools that feels intimidating until you understand the underlying model, at which point the command-line interface stops being confusing and starts being logical. This guide is about both: the commands you need to run and why the design works the way it does.</p>
<p>The idea is to split your GPG identity into a master key you keep offline and subkeys you use day-to-day. If your machine is ever compromised, an attacker gets the subkeys — you revoke them, generate new ones, and your identity is intact.</p>
<h2 id="what-gpg-is-actually-for">What GPG is actually for</h2>
<p>A GPG key pair has two common uses:</p>
<ul>
<li><strong>Signing</strong> — attaching a cryptographic signature to something (a commit, a message, a package) so others can verify it came from you</li>
<li><strong>Encryption</strong> — encrypting something so only the holder of a specific private key can read it</li>
</ul>
<h2 id="creating-the-master-key-pair">Creating the master key pair</h2>
<p>With GnuPG installed, start with:</p>
<pre><code class="language-sh">gpg --expert --full-generate-key
</code></pre>
<p>You will be asked to choose a key type:</p>
<pre><code>Please select what kind of key you want:
   (1) RSA and RSA
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)
   (7) DSA (set your own capabilities)
   (8) RSA (set your own capabilities)
   (9) ECC (sign and encrypt) *default*
  (10) ECC (sign only)
  (11) ECC (set your own capabilities)
  (13) Existing key
  (14) Existing key from card
  (16) ECC and Kyber
</code></pre>
<p>Choose <code>ECC (set your own capabilities)</code> (option 11). RSA was the previous default, but ECC provides equivalent security with much smaller key sizes and faster operations. Neither RSA nor ECC is quantum-resistant, but ECC is the better option for classical threat models and is where the ecosystem has moved. Option 16 (ECC and Kyber) is an experimental post-quantum hybrid, but it lacks broad tooling support and is not recommended for general use yet.</p>
<p>Option 11 lets you control exactly which capabilities the master key has. GPG defaults to Sign and Certify, but the master key should only certify — signing data directly from the master key defeats the purpose of having subkeys. Toggle Sign off:</p>
<pre><code>Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify

   (S) Toggle the sign capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? S

Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Certify

   (S) Toggle the sign capability
   (A) Toggle the authenticate capability
   (Q) Finished

Your selection? Q
</code></pre>
<p>Enter <code>S</code> to remove the Sign capability, then <code>Q</code> to confirm. The master key will carry <code>[C]</code> only.</p>
<p>You will then be asked to choose a curve:</p>
<pre><code>Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (2) Curve 448
   (3) NIST P-256
   (4) NIST P-384
   (5) NIST P-521
   (6) Brainpool P-256
   (7) Brainpool P-384
   (8) Brainpool P-512
</code></pre>
<p>Choose <code>Curve 25519</code> (option 1). It uses ed25519 for signing — a well-vetted, modern algorithm with strong performance and wide support.</p>
<p>Next, you will be asked for an expiry:</p>
<pre><code>Please specify how long the key should be valid.
         0 = key does not expire
      &#x3C;n>  = key expires in n days
      &#x3C;n>w = key expires in n weeks
      &#x3C;n>m = key expires in n months
      &#x3C;n>y = key expires in n years
Key is valid for? (0)
</code></pre>
<p>Set an expiry of one to two years. It might seem counterintuitive, but expiry is a safety net, not a limitation. You can extend it any time before it lapses. What you cannot do is un-publish a non-expiring key you have lost access to. An expired key at least communicates that it is no longer active.</p>
<p>After filling in your name, email, and a passphrase, GPG prints the result:</p>
<pre><code>pub   ed25519 2026-03-31 [C] [expires: 2027-03-31]
      E31C074FA2D89B561C4A730F852E1D493BC60A7F
uid                      Adam Brauns &#x3C;adam@example.com>
</code></pre>
<p>You can confirm it appears in your keyring:</p>
<pre><code class="language-sh">gpg --list-secret-keys
</code></pre>
<pre><code>sec   ed25519 2026-03-31 [C] [expires: 2027-03-31]
      E31C074FA2D89B561C4A730F852E1D493BC60A7F
uid           [ultimate] Adam Brauns &#x3C;adam@example.com>
</code></pre>
<h2 id="reading-the-output">Reading the output</h2>
<p>The labels in the output carry meaning worth understanding before you go further.</p>
<p><code>sec</code> is your secret master key. <code>ssb</code> is a secret subkey. <code>pub</code> and <code>sub</code> are the public counterparts of each. The bracketed letters describe what the key is authorized to do:</p>
<div class="table-scroll"><table>
<thead>
<tr>
<th align="left">Label</th>
<th align="left">Capability</th>
<th align="left">Notes</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>C</code></td>
<td align="left">Certify</td>
<td align="left">Sign other keys. Only the master key has this.</td>
</tr>
<tr>
<td align="left"><code>S</code></td>
<td align="left">Sign</td>
<td align="left">Sign data, commits, and messages.</td>
</tr>
<tr>
<td align="left"><code>E</code></td>
<td align="left">Encrypt</td>
<td align="left">Encrypt and decrypt.</td>
</tr>
<tr>
<td align="left"><code>A</code></td>
<td align="left">Authenticate</td>
<td align="left">Rarely used; some SSH implementations support GPG auth.</td>
</tr>
</tbody>
</table></div>
<p>The master key carries <code>[C]</code> only: it can certify but not sign data directly. Certify is what lets the master key generate subkeys, and it cannot be delegated. Subkeys can sign and encrypt but cannot spawn their own subkeys.</p>
<h2 id="adding-a-signing-subkey">Adding a signing subkey</h2>
<p>Open the key for editing:</p>
<pre><code class="language-sh">gpg --edit-key E31C074FA2D89B561C4A730F852E1D493BC60A7F
</code></pre>
<p>At the <code>gpg></code> prompt, type <code>addkey</code>:</p>
<pre><code>Please select what kind of key you want:
   (3) DSA (sign only)
   (4) RSA (sign only)
   (5) Elgamal (encrypt only)
   (6) RSA (encrypt only)
  (10) ECC (sign only)
  (12) ECC (encrypt only)
  (14) Existing key from card
</code></pre>
<p>Choose option <code>10</code> for an ECC signing subkey.</p>
<pre><code>Please select which elliptic curve you want:
   (1) Curve 25519 *default*
   (4) NIST P-384
   (6) Brainpool P-256
</code></pre>
<p>Choose option <code>1</code> for elliptic curve.</p>
<p>Set the same expiry as your master key so they stay in sync, and enter your passphrase when prompted.</p>
<p>Once done, the key listing inside the editor should show two keys:</p>
<pre><code>sec  ed25519/95BB0086DDD86937
     created: 2026-03-31  expires: 2027-03-31  usage: C
     trust: ultimate      validity: ultimate
ssb  ed25519/14072A8493246577
     created: 2026-03-31  expires: 2027-03-31  usage: S
[ultimate] (1). Adam Brauns &#x3C;adam@example.com>
</code></pre>
<p>If you also want an encryption subkey, run <code>addkey</code> again and choose option <code>12</code> (ECC encrypt only). Select Curve 25519 and set the same expiry. The key listing will then show a third entry with <code>usage: E</code>. Note that even though both subkeys use Curve 25519, GPG generates different algorithms under the hood — ed25519 for signing and cv25519 (X25519) for encryption. That is why the final <code>--list-secret-keys</code> output shows different algorithm labels for each subkey.</p>
<p>Type <code>save</code> and press enter. Your keyring now has a master key for certification and two subkeys — one for encryption, one for signing. That is the complete set you need before moving the master key offline.</p>
<h2 id="exporting-and-backing-up">Exporting and backing up</h2>
<p>Before removing anything from your local machine, export all three components. Adjust the filenames and key ID to match your setup:</p>
<pre><code class="language-sh">gpg --output adam.public.gpg --export E31C074FA2D89B561C4A730F852E1D493BC60A7F
gpg --output adam.secret.gpg --export-secret-key E31C074FA2D89B561C4A730F852E1D493BC60A7F
gpg --output adam.secsub.gpg --export-secret-subkeys E31C074FA2D89B561C4A730F852E1D493BC60A7F
</code></pre>
<p>The public key export is what you share with the world — paste it into GitHub, upload it to a keyserver, whatever fits your workflow. The secret key export contains everything and goes to offline storage only. The secret subkeys export is what you will re-import onto your local machine after removing the master.</p>
<p>Before transferring these to your offline storage, you might also want to generate a revocation certificate:</p>
<pre><code class="language-sh">gpg --output adam.revoke.asc --gen-revoke E31C074FA2D89B561C4A730F852E1D493BC60A7F
</code></pre>
<p>A revocation certificate lets you publicly invalidate the key even if you no longer have access to it. Store it alongside your other exports. If you ever lose both your machine and your backup, this is your last resort.</p>
<p>Copy all the files to a USB drive or external hard drive that you keep somewhere physically secure. These are your only copies of the master secret key once the next step is done, so the backup matters.</p>
<h2 id="removing-the-master-key-from-your-local-machine">Removing the master key from your local machine</h2>
<p>This step is what most guides skip. Delete the entire secret key from your local keyring — this removes both the master key and the subkeys:</p>
<pre><code class="language-sh">gpg --delete-secret-keys E31C074FA2D89B561C4A730F852E1D493BC60A7F
</code></pre>
<p>GPG will ask you to confirm. Now your local keyring has no private keys at all. Re-import just the subkeys:</p>
<pre><code class="language-sh">gpg --import adam.secsub.gpg
</code></pre>
<p>Verify the result:</p>
<pre><code class="language-sh">gpg --list-secret-keys
</code></pre>
<pre><code>sec#  ed25519 2026-03-31 [C] [expires: 2027-03-31]
      E31C074FA2D89B561C4A730F852E1D493BC60A7F
uid           [ultimate] Adam Brauns &#x3C;adam@example.com>
ssb   ed25519 2026-03-31 [S] [expires: 2027-03-31]
</code></pre>
<p>The <code>#</code> after <code>sec</code> is the confirmation you are looking for. It means GPG knows about the master key's public component and fingerprint but does not have the private key on this machine. The two <code>ssb</code> entries are fully present and ready to use for signing and encrypting.</p>
<p>Delete the exported files from your local machine. They belong on the offline backup and nowhere else.</p>
<h2 id="day-to-day-use">Day-to-day use</h2>
<p>For most tasks, the subkey setup is invisible. Signing a git commit, encrypting a file, or decrypting a message all work exactly as they would with the master key present.</p>
<p>The one thing you cannot do without re-importing the master key is change the key itself: adding or revoking subkeys, updating expiry dates, or certifying someone else's key. For any of those operations, temporarily import from your backup:</p>
<pre><code class="language-sh">gpg --import /path/to/backup/adam.secret.gpg
</code></pre>
<p>Make your changes, then remove the master key again and re-import just the subkeys. Keeping that cycle explicit is the whole point — the master key touches the machine only when it has to.</p>
<p>When your keys approach expiry, connect the backup, re-import, extend the expiry date on both the master and the subkeys, and put it back offline. Setting a reminder a few weeks before expiry is worth doing so you are not scrambling at the last minute.</p>
<h2 id="what-you-get-from-this">What you get from this</h2>
<p>The setup limits the consequence of a worst-case scenario. If your machine is stolen or your keyring is compromised, an attacker has your subkeys. That means they can sign things as you and decrypt things addressed to you — which is bad, but recoverable. You revoke the subkeys with the master, generate new ones, and your long-term GPG identity is intact.</p>
<p>Without subkeys, the master key being on your laptop means losing the device is equivalent to losing your identity. Every signature you have ever made, every trust relationship in the web of trust — those are all associated with that key. Rebuilding from scratch means losing all of it.</p>
<p>The subkey model is not exotic or complicated once it is set up. It is just keeping the thing that matters most somewhere that is not your everyday machine.</p>
]]></content:encoded>
        </item>
<item>
          <title>Making &quot;Dumb&quot; Devices Smart with an IR Blaster and Home Assistant</title>
          <link>https://adambrauns.com/blog/ir-blaster-smart-home</link>
          <guid>https://adambrauns.com/blog/ir-blaster-smart-home</guid>
          <pubDate>Sun, 08 Mar 2026 18:22:00 GMT</pubDate>
          <description>A practical guide to using BroadLink RM4 devices with Home Assistant to automate remote-controlled hardware you already own.</description>
          <content:encoded><![CDATA[<p>I originally went down this path because I wanted a better way to control the tower fans around my house. There are plenty of Wi-Fi-enabled fans on the market, but paying more just to get app control felt unnecessary when I already owned perfectly good hardware with a remote.</p>
<p>That was my immediate use case, but the pattern is much broader than tower fans. If a device already responds to an infrared remote, it is often a good candidate for this kind of retrofit. TVs, sound bars, air conditioners, fireplaces, LED controllers, and a lot of other "dumb" appliances can be pulled into Home Assistant without replacing them. With the right hardware, some RF-based devices can too.</p>
<p>I used BroadLink because <a href="https://www.home-assistant.io/integrations/broadlink/">Home Assistant has a straightforward Broadlink integration</a>, but the general idea applies to any IR blaster Home Assistant can control reliably.</p>
<h2 id="why-this-approach-made-sense">Why this approach made sense</h2>
<p>The key insight is that many so-called "dumb" devices are not actually dumb at all. They already accept remote commands over IR, and sometimes RF. That means you do not necessarily need new hardware, new motors, or a cloud-connected replacement. You just need a bridge that can replay the same commands from Home Assistant.</p>
<p>An IR or RF blaster is a good middle ground for exactly that kind of problem:</p>
<ul>
<li>Keep the hardware you already own</li>
<li>Avoid the cost of replacing multiple devices</li>
<li>Add scheduling, scenes, and automations through Home Assistant</li>
<li>Keep control local once the commands are learned in Home Assistant</li>
</ul>
<p>For me, that was much more appealing than rebuying working devices just to get app control.</p>
<h2 id="where-this-works-well">Where this works well</h2>
<p>This approach is especially useful for devices that already have simple remotes and do not need rich two-way state reporting. A few common examples:</p>
<ul>
<li>TVs, projectors, and sound bars</li>
<li>Portable AC units and mini-split remotes</li>
<li>Tower fans and space heaters with IR remotes</li>
<li>Electric fireplaces or LED light controllers</li>
<li>Shades, switches, and other RF devices if you choose hardware that supports RF</li>
</ul>
<p>The less a device depends on reporting its actual state back to you, the better this pattern tends to work.</p>
<h2 id="rm4-mini-vs-rm4-pro">RM4 mini vs RM4 Pro</h2>
<p>BroadLink has a few variations in this family, but the decision most people care about is the difference between the RM4 mini and the RM4 Pro.</p>
<div class="table-scroll"><table>
<thead>
<tr>
<th align="left">Device</th>
<th align="left">Best For</th>
<th align="left">Protocols</th>
<th align="left">When It Makes Sense</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><a href="https://amzn.to/4uhX0kZ">RM4 mini</a></td>
<td align="left">IR-only appliances like TVs, fans, AC units, and media gear</td>
<td align="left">IR</td>
<td align="left">Use this when every device you want to control already uses a standard IR remote. It is the simpler and usually cheaper option.</td>
</tr>
<tr>
<td align="left"><a href="https://amzn.to/40kviq4">RM4 Pro</a></td>
<td align="left">Mixed IR and RF setups like shades, projector screens, switches, and some blinds</td>
<td align="left">IR + RF</td>
<td align="left">Use this when at least one of your target devices uses RF, or when you want one bridge that can cover more than just infrared gear.</td>
</tr>
</tbody>
</table></div>
<p>I used the RM4 mini because my tower fans were standard IR devices. If I were trying to control a mix of IR devices and RF-controlled shades or switches, I would start with the RM4 Pro instead.</p>
<p>One important caveat with the RM4 Pro: BroadLink positions it for RF devices, but it does not support rolling code remotes. In practice, that means it can be a good fit for some RF devices, but not everything with an RF remote is going to be compatible.</p>
<h2 id="what-i-used">What I used</h2>
<ul>
<li>A <a href="https://amzn.to/4uhX0kZ">BroadLink RM4 mini</a> for my setup</li>
<li>The <a href="https://amzn.to/40kviq4">BroadLink RM4 Pro</a> as the better option if you need RF support</li>
<li><a href="https://www.home-assistant.io/integrations/broadlink/">Home Assistant's Broadlink integration</a></li>
<li>The original remotes for the devices I wanted to control</li>
<li>The BroadLink mobile app for initial setup</li>
</ul>
<p>The official Home Assistant docs note that the manufacturer app is required to get new BroadLink devices onto the network first, which matched my setup experience.</p>
<h2 id="how-the-setup-works">How the setup works</h2>
<p>The BroadLink device learns the same commands your physical remote sends. Home Assistant then stores those commands by device and command name, and later replays them through the BroadLink remote using <code>remote.send_command</code>.</p>
<p>That gives you a nice upgrade path for older appliances:</p>
<ul>
<li>Learn each device's important commands once</li>
<li>Wrap those commands in scripts, scenes, or automations</li>
<li>Trigger them from dashboards, schedules, or voice assistants connected to Home Assistant</li>
</ul>
<p>The important limitation is that IR is one-way, and many RF remotes are similar from Home Assistant's point of view. Home Assistant can send commands, but it usually does not know the true state of the device unless you model that state yourself with helpers or careful automations. For things like routines, scenes, and simple control surfaces, that tradeoff is often fine.</p>
<h2 id="setup-steps">Setup steps</h2>
<p>Here is the setup flow I would recommend for most BroadLink installs.</p>
<h3>Initial device setup</h3>
<ol>
<li>Install the BroadLink mobile app and follow its setup flow to get the RM4 device connected to your Wi-Fi network.</li>
<li>Open the device settings in the BroadLink app and make sure <code>Lock device</code> is disabled so Home Assistant can communicate with it properly.</li>
<li>Give the BroadLink device a stable IP address, ideally through a DHCP reservation on your router, so Home Assistant can always find it reliably.</li>
<li>If the device is not auto-discovered, enter the device's IP address manually during setup.</li>
</ol>
<h3>Add it to Home Assistant and learn commands</h3>
<ol>
<li>Add the BroadLink integration in Home Assistant and complete the setup prompts.</li>
<li>Open <code>Developer Tools</code> -> <code>Actions</code>.</li>
<li>Select the <code>remote.learn_command</code> action.</li>
<li>Fill in the action details:
<ul>
<li><code>Target</code>: Select the BroadLink remote entity</li>
<li><code>Device</code>: Enter a stable name for the device you are teaching, such as <code>tower_fan</code></li>
<li><code>Command</code>: Enter the specific button name you want to learn, such as <code>Power</code></li>
</ul>
</li>
<li>Click <code>Perform Action</code> to put the BroadLink device into learning mode.</li>
<li>Point the original remote at the BroadLink device and press the matching button.</li>
<li>Repeat the process for every command you want Home Assistant to be able to replay later.</li>
</ol>
<p>This is also the point where naming matters. I found it easiest to pick a stable device name once and then keep command names human-readable.</p>
<h2 id="my-tower-fan-example">My tower fan example</h2>
<p>Tower fans were the thing that pushed me to set this up in the first place, so here is the concrete example from my house. I grouped everything under the <code>tower_fan</code> device name and taught Home Assistant the following commands:</p>
<div class="table-scroll"><table>
<thead>
<tr>
<th align="left">Entity Name</th>
<th align="left">Command</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">tower_fan</td>
<td align="left">Power</td>
</tr>
<tr>
<td align="left">tower_fan</td>
<td align="left">Power Up</td>
</tr>
<tr>
<td align="left">tower_fan</td>
<td align="left">Power Down</td>
</tr>
<tr>
<td align="left">tower_fan</td>
<td align="left">Rotate</td>
</tr>
<tr>
<td align="left">tower_fan</td>
<td align="left">Mode</td>
</tr>
<tr>
<td align="left">tower_fan</td>
<td align="left">Timer</td>
</tr>
<tr>
<td align="left">tower_fan</td>
<td align="left">Silence</td>
</tr>
</tbody>
</table></div>
<p>Once these are learned, Home Assistant can call them at any time with <code>remote.send_command</code>.</p>
<h2 id="example-home-assistant-scripts">Example Home Assistant scripts</h2>
<p>After learning the commands, I wrapped a few of them in scripts so they were easier to reuse from automations and dashboards. This example is fan-specific, but the same pattern works for any supported device.</p>
<pre><code class="language-yaml">script:
  tower_fan_power_toggle:
    alias: Tower Fan Power Toggle
    sequence:
      - action: remote.send_command
        target:
          entity_id: remote.office_ir
        data:
          device: tower_fan
          command: Power

  tower_fan_rotate:
    alias: Tower Fan Rotate
    sequence:
      - action: remote.send_command
        target:
          entity_id: remote.office_ir
        data:
          device: tower_fan
          command: Rotate

  tower_fan_quiet_mode:
    alias: Tower Fan Quiet Mode
    sequence:
      - action: remote.send_command
        target:
          entity_id: remote.office_ir
        data:
          device: tower_fan
          command: Silence
</code></pre>
<p>From there, you can drop those scripts onto a dashboard, expose them to voice assistants, or call them from automations.</p>
<h2 id="a-simple-automation-example">A simple automation example</h2>
<p>One nice use case is bundling commands into a routine. Here is a simple example that kicks my fans into a quieter mode at night:</p>
<pre><code class="language-yaml">automation:
  - alias: Tower Fan Quiet Down At Night
    triggers:
      - trigger: time
        at: "21:30:00"
    actions:
      - action: script.tower_fan_quiet_mode
      - delay: "00:00:01"
      - action: script.tower_fan_rotate
</code></pre>
<p>If your fan has a single power toggle instead of separate on and off commands, keep that in mind when building automations. Toggle-based IR devices are easy to control, but they are not perfectly state-aware unless you add your own tracking.</p>
<h2 id="when-this-approach-makes-the-most-sense">When this approach makes the most sense</h2>
<p>The biggest win is that it makes existing hardware more useful without forcing a full replacement cycle. If you already own a device that works well and it has a remote, this is often one of the cheapest ways to pull it into a smart home.</p>
<p>This approach is especially good when:</p>
<ul>
<li>Centralized control from Home Assistant</li>
<li>You want schedules, scenes, and routines</li>
<li>The device is already remote-controlled and otherwise works fine</li>
<li>You want to avoid paying a premium for Wi-Fi versions of things you already own</li>
</ul>
<p>It makes less sense when the device depends heavily on accurate state tracking, uses unsupported RF behavior like rolling codes, or needs bi-directional feedback to be reliable.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>The useful takeaway here is not really about tower fans. It is that a lot of remote-controlled devices can be brought into Home Assistant without replacing hardware that already works.</p>
<p>If the device uses IR, the <a href="https://amzn.to/4uhX0kZ">RM4 mini</a> is usually the straightforward choice. If you need compatible RF support as well, the <a href="https://amzn.to/40kviq4">RM4 Pro</a> is the better option.</p>
<p>If you are happy with the physical device and just want it to participate in automations, scenes, and dashboards, an IR or RF bridge is often the cheapest upgrade path available. In many cases, you do not need a new "smart" version of the device. You just need a way to teach Home Assistant the remote commands it already understands.</p>
]]></content:encoded>
        </item>
  </channel>
</rss>