<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yuus_company</title>
    <description>The latest articles on DEV Community by yuus_company (@yuus_company).</description>
    <link>https://dev.to/yuus_company</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4111813%2F37d15b24-4f77-4bb9-9325-301a4c4579be.png</url>
      <title>DEV Community: yuus_company</title>
      <link>https://dev.to/yuus_company</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yuus_company"/>
    <language>en</language>
    <item>
      <title>API key design: entropy math, prefixes, and why sk_live_ is genius</title>
      <dc:creator>yuus_company</dc:creator>
      <pubDate>Sun, 06 Sep 2026 04:41:06 +0000</pubDate>
      <link>https://dev.to/yuus_company/api-key-design-entropy-math-prefixes-and-why-sklive-is-genius-44fp</link>
      <guid>https://dev.to/yuus_company/api-key-design-entropy-math-prefixes-and-why-sklive-is-genius-44fp</guid>
      <description>&lt;p&gt;At some point every backend grows an API key system. Most teams improvise the format — and the format matters more than you'd think. Here's the 10-minute version of what I wish I'd known.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Length is entropy — do the math once
&lt;/h2&gt;

&lt;p&gt;The security of a key comes from its random body. With a character set of size &lt;strong&gt;C&lt;/strong&gt; and body length &lt;strong&gt;L&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;entropy = L × log₂(C) bits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Some real numbers for the usual &lt;code&gt;A-Za-z0-9&lt;/code&gt; set (C = 62, log₂ ≈ 5.95):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Body length&lt;/th&gt;
&lt;th&gt;Entropy&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;16 chars&lt;/td&gt;
&lt;td&gt;~95 bits&lt;/td&gt;
&lt;td&gt;weak-ish for a public key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22 chars&lt;/td&gt;
&lt;td&gt;~131 bits&lt;/td&gt;
&lt;td&gt;the common baseline (≥128 bits)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32 chars&lt;/td&gt;
&lt;td&gt;~190 bits&lt;/td&gt;
&lt;td&gt;comfortable margin, still short enough to handle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you exclude look-alike characters (&lt;code&gt;0/O&lt;/code&gt;, &lt;code&gt;1/l/I&lt;/code&gt;) for human readability, the set shrinks — just add 2–3 characters of length and you're back to the same strength.&lt;/p&gt;

&lt;p&gt;(A UUID v4, by the way, is 122 bits. It clears the bar, but keeps reading...)&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The prefix is not decoration
&lt;/h2&gt;

&lt;p&gt;Stripe's &lt;code&gt;sk_live_&lt;/code&gt; / &lt;code&gt;sk_test_&lt;/code&gt; and GitHub's &lt;code&gt;ghp_&lt;/code&gt; prefixes do three jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environment safety&lt;/strong&gt; — you can tell at a glance (and assert in code!) that a test key never hits production endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incident triage&lt;/strong&gt; — when a key fragment shows up in a log or error report, the prefix tells you instantly which environment leaked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret scanning&lt;/strong&gt; — this is the big one. A distinctive prefix is a regex-able pattern. GitHub secret scanning, truffleHog, and friends can detect your keys in public commits and auto-revoke them. A bare random string or UUID is invisible to these tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why "just use a UUID" is the wrong answer even though the entropy is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Storage rules (the part everyone gets wrong)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hash the key server-side&lt;/strong&gt; (SHA-256 is fine — keys are high-entropy, unlike passwords, so you don't need bcrypt's slowness). Store the hash, compare hashes on auth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show the plaintext exactly once&lt;/strong&gt;, at issue time. If the user loses it, they rotate.&lt;/li&gt;
&lt;li&gt;Keep a &lt;strong&gt;short displayable suffix&lt;/strong&gt; (&lt;code&gt;...x7Kq&lt;/code&gt;) so users can identify keys in a dashboard without you storing the secret.&lt;/li&gt;
&lt;li&gt;Track &lt;strong&gt;created / expires / last-used&lt;/strong&gt; per key and reap unused ones.&lt;/li&gt;
&lt;li&gt;Have a &lt;strong&gt;rotation path&lt;/strong&gt; before you need it: issue new → dual-accept window → revoke old.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Prototyping a format
&lt;/h2&gt;

&lt;p&gt;Before committing to a format, it's worth generating a batch and looking at it: does it survive double-click selection? Does it wrap badly in your docs? Is the chunked version (&lt;code&gt;XXXX-XXXX-...&lt;/code&gt;) actually more readable for your case?&lt;/p&gt;

&lt;p&gt;I put together a small &lt;a href="https://json2class.com/en/api-key-generator/" rel="noopener noreferrer"&gt;API key generator&lt;/a&gt; for exactly this — configurable prefix, body length, chunking, and character rules, generated locally with &lt;code&gt;crypto.getRandomValues&lt;/code&gt; (nothing sent to any server). Handy for staging keys and for eyeballing format candidates with your team.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;≥128 bits of entropy: 22+ random chars over &lt;code&gt;A-Za-z0-9&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Always use a distinctive prefix — it's what makes secret scanning work.&lt;/li&gt;
&lt;li&gt;Store hashes, show plaintext once, track usage, plan rotation.&lt;/li&gt;
&lt;li&gt;UUIDs pass the entropy test but fail the operability test.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>api</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>No, you can't decrypt a bcrypt hash — here's what to do instead</title>
      <dc:creator>yuus_company</dc:creator>
      <pubDate>Sun, 06 Sep 2026 04:41:04 +0000</pubDate>
      <link>https://dev.to/yuus_company/no-you-cant-decrypt-a-bcrypt-hash-heres-what-to-do-instead-4f4j</link>
      <guid>https://dev.to/yuus_company/no-you-cant-decrypt-a-bcrypt-hash-heres-what-to-do-instead-4f4j</guid>
      <description>&lt;p&gt;"bcrypt decode", "bcrypt hash to text", "bcrypt decrypt online" — these searches get thousands of hits a month, and every result promising to do it is either confused or a scam. Let's clear this up properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  bcrypt is a hash, not encryption
&lt;/h2&gt;

&lt;p&gt;Encryption is a two-way street: encrypt with a key, decrypt with a key. A &lt;strong&gt;hash&lt;/strong&gt; is a one-way function by design. bcrypt takes a password and produces a fixed digest; there is no key, and no inverse function exists. This isn't a limitation to work around — it's the entire point. If a database leaks, the attacker gets digests, not passwords.&lt;/p&gt;

&lt;p&gt;So when someone asks "how do I decrypt this bcrypt hash?", the real question is almost always one of these three:&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 1: "I need to check if a password matches"
&lt;/h2&gt;

&lt;p&gt;You never decode the hash — you hash the candidate password &lt;strong&gt;with the same salt&lt;/strong&gt; and compare. Every bcrypt library does this for you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Node.js&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;bcryptjs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hunter2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;storedHash&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// true / false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Python
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;bcrypt&lt;/span&gt;
&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bcrypt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;checkpw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hunter2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stored_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The salt is embedded in the hash string itself (that's what the &lt;code&gt;$2b$10$...&lt;/code&gt; prefix carries), so &lt;code&gt;compare&lt;/code&gt; needs nothing else.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 2: "A user forgot their password"
&lt;/h2&gt;

&lt;p&gt;You can't recover it, and that's correct behavior. The only legitimate flow is a &lt;strong&gt;reset&lt;/strong&gt;: send a time-limited, single-use token to a verified channel, let the user set a new password, hash the new one. Any site that can email you your &lt;em&gt;old&lt;/em&gt; password is storing it wrong — treat that as a red flag as a user, and never build it as a developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case 3: "I'm testing / doing a CTF and need to crack it"
&lt;/h2&gt;

&lt;p&gt;What people call "decrypting" here is really &lt;strong&gt;guessing&lt;/strong&gt;: hash candidate passwords until one matches. Tools like hashcat automate this against wordlists. This is exactly the attack bcrypt is built to resist — its work factor (cost) makes each guess slow, so strong passwords remain out of reach even offline. It works only against weak passwords, and only do this on systems you're authorized to test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading a bcrypt string
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$2b$10$N9qo8uLOickgx2ZMRZoMye.IjPeVUxkJDrz8vd3AJlDpMH0W3iBGe
 │  │  └──────────┬─────────┘└──────────────┬───────────────┘
 │  │            salt (22 chars)          digest (31 chars)
 │  └─ cost factor: 2¹⁰ = 1,024 rounds
 └─ algorithm version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cost factor is the tunable part. 10 is a common default; raise it as hardware gets faster. Each +1 doubles the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;If you want to see how the same password produces different hashes (random salt) and how &lt;code&gt;compare&lt;/code&gt; still works, I built a small &lt;a href="https://json2class.com/en/bcrypt-hash-generator/" rel="noopener noreferrer"&gt;bcrypt hash generator&lt;/a&gt; that runs entirely in the browser — generate, tweak the cost factor, and verify, with nothing sent to a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;bcrypt cannot be decrypted. Nothing can "convert a bcrypt hash to text."&lt;/li&gt;
&lt;li&gt;To verify: &lt;code&gt;bcrypt.compare(candidate, hash)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Forgot password → reset flow, never recovery.&lt;/li&gt;
&lt;li&gt;"Cracking" = brute-force guessing, which bcrypt deliberately makes slow.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>node</category>
    </item>
    <item>
      <title>JSON to Kotlin data class: Gson vs Moshi vs kotlinx.serialization annotations</title>
      <dc:creator>yuus_company</dc:creator>
      <pubDate>Sun, 06 Sep 2026 04:41:02 +0000</pubDate>
      <link>https://dev.to/yuus_company/json-to-kotlin-data-class-gson-vs-moshi-vs-kotlinxserialization-annotations-34go</link>
      <guid>https://dev.to/yuus_company/json-to-kotlin-data-class-gson-vs-moshi-vs-kotlinxserialization-annotations-34go</guid>
      <description>&lt;p&gt;Turning a JSON payload into a Kotlin data class looks trivial until you hit the details: which annotations, what about &lt;code&gt;snake_case&lt;/code&gt;, what happens to missing fields? The answers differ meaningfully across the three mainstream libraries. Here's the same payload, three ways.&lt;/p&gt;

&lt;p&gt;The JSON:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"u-1042"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yush"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"is_verified"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"follower_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1204&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"last_login"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gson
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"is_verified"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"follower_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;followerCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerializedName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"last_login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;lastLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The catch everyone learns the hard way: &lt;strong&gt;Gson ignores Kotlin null-safety&lt;/strong&gt;. It uses reflection and will happily write &lt;code&gt;null&lt;/code&gt; into a non-nullable &lt;code&gt;val&lt;/code&gt; if the field is missing — you get the crash later, far from the parse site. Default values are also ignored unless every field has one. Gson is fine for legacy codebases, but it predates Kotlin and it shows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Moshi (with KSP codegen)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@JsonClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generateAdapter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"is_verified"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"follower_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;followerCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@Json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"last_login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;lastLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With codegen (&lt;code&gt;generateAdapter = true&lt;/code&gt;), Moshi &lt;strong&gt;enforces&lt;/strong&gt; nullability at parse time: a missing non-nullable field throws a clear &lt;code&gt;JsonDataException&lt;/code&gt; naming the field. Default values work as expected. This is the pragmatic choice for Android projects already on OkHttp/Retrofit.&lt;/p&gt;

&lt;h2&gt;
  
  
  kotlinx.serialization
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Serializable&lt;/span&gt;
&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;UserProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"user_id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;displayName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"is_verified"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;isVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"follower_count"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;followerCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;@SerialName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"last_login"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;lastLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compiler-plugin based, zero reflection, multiplatform (JVM/JS/Native/Wasm). Two behaviors worth knowing: unknown JSON keys &lt;strong&gt;fail by default&lt;/strong&gt; (turn on &lt;code&gt;ignoreUnknownKeys = true&lt;/code&gt; in the &lt;code&gt;Json {}&lt;/code&gt; config for API work), and fields with default values are skipped during serialization unless you set &lt;code&gt;encodeDefaults = true&lt;/code&gt;. If you're starting fresh or targeting KMP, this is the one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Gson&lt;/th&gt;
&lt;th&gt;Moshi (codegen)&lt;/th&gt;
&lt;th&gt;kotlinx.serialization&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Null-safety enforced&lt;/td&gt;
&lt;td&gt;❌ silently breaks&lt;/td&gt;
&lt;td&gt;✅ throws clearly&lt;/td&gt;
&lt;td&gt;✅ throws clearly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Default values&lt;/td&gt;
&lt;td&gt;unreliable&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reflection at runtime&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no (codegen)&lt;/td&gt;
&lt;td&gt;no (compiler plugin)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multiplatform&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;legacy code&lt;/td&gt;
&lt;td&gt;Android + Retrofit&lt;/td&gt;
&lt;td&gt;new code, KMP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Generating the boilerplate
&lt;/h2&gt;

&lt;p&gt;Writing &lt;code&gt;@SerialName&lt;/code&gt; for thirty fields by hand is nobody's idea of fun. I use &lt;a href="https://json2class.com/en/json-to-kotlin/" rel="noopener noreferrer"&gt;this JSON to Kotlin converter&lt;/a&gt; — paste the payload, and it derives the data class with nested classes and nullable types from the actual values, in the browser (nothing uploaded). Then switch the annotations to whichever library your project uses.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Gson silently violates Kotlin null-safety; prefer Moshi or kotlinx.serialization.&lt;/li&gt;
&lt;li&gt;Moshi: best drop-in for Android/Retrofit stacks.&lt;/li&gt;
&lt;li&gt;kotlinx.serialization: default for new projects and multiplatform — remember &lt;code&gt;ignoreUnknownKeys&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kotlin</category>
      <category>android</category>
      <category>json</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Avro schemas for Kafka developers: the 10-minute practical guide</title>
      <dc:creator>yuus_company</dc:creator>
      <pubDate>Sun, 06 Sep 2026 04:41:00 +0000</pubDate>
      <link>https://dev.to/yuus_company/avro-schemas-for-kafka-developers-the-10-minute-practical-guide-2p75</link>
      <guid>https://dev.to/yuus_company/avro-schemas-for-kafka-developers-the-10-minute-practical-guide-2p75</guid>
      <description>&lt;p&gt;If you work with Kafka long enough, someone puts Avro in front of you. The docs are dense, so here's the working knowledge you actually need: what a schema looks like, the two rules that cause 90% of production errors, and how to debug a binary payload.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Avro instead of JSON?
&lt;/h2&gt;

&lt;p&gt;Two reasons: &lt;strong&gt;size&lt;/strong&gt; (binary encoding, field names aren't repeated in every message — a JSON message of 500 bytes often becomes ~100) and &lt;strong&gt;contracts&lt;/strong&gt; (the schema is enforced at produce time, so a malformed message never enters the topic). With a schema registry, consumers always know how to decode what producers wrote — including messages written under older schema versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The schema, minimally
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"record"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OrderCreated"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"namespace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.shop.events"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"orderId"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"amount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"long"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"KRW"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"couponCode"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"null"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"default"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parts that trip people up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;There is no "optional" keyword.&lt;/strong&gt; An optional field is a union with null — &lt;code&gt;["null", "string"]&lt;/code&gt; — and the default must be &lt;code&gt;null&lt;/code&gt;, with &lt;code&gt;"null"&lt;/code&gt; listed &lt;strong&gt;first&lt;/strong&gt; in the union. Order matters; &lt;code&gt;["string", "null"]&lt;/code&gt; with a null default is invalid.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defaults are not runtime fallbacks.&lt;/strong&gt; A default is used when a &lt;em&gt;reader&lt;/em&gt; decodes data written by an &lt;em&gt;older schema&lt;/em&gt; that lacked the field. Producers still must set every field.&lt;/li&gt;
&lt;li&gt;Timestamps are &lt;code&gt;long&lt;/code&gt; with a logical type: &lt;code&gt;{ "type": "long", "logicalType": "timestamp-millis" }&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The two evolution rules that matter
&lt;/h2&gt;

&lt;p&gt;Schema evolution is why Avro exists, and it boils down to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adding a field?&lt;/strong&gt; It must have a default. Otherwise old messages can't be read with the new schema (&lt;code&gt;BACKWARD&lt;/code&gt; compatibility breaks — the registry will reject it).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Removing a field?&lt;/strong&gt; Only remove fields that &lt;em&gt;had&lt;/em&gt; a default. Never rename — a rename is a remove plus an add, and old data loses the value silently. Add an alias instead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything else (changing types, reordering unions) — check against your registry's compatibility mode before assuming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging: "what's actually in this message?"
&lt;/h2&gt;

&lt;p&gt;Binary Avro is unreadable in &lt;code&gt;kafka-console-consumer&lt;/code&gt;, and the classic gotcha is the &lt;strong&gt;magic byte&lt;/strong&gt;: messages produced through Confluent serializers carry a 5-byte header (&lt;code&gt;0x00&lt;/code&gt; + 4-byte schema ID) before the Avro payload. If your decoder chokes immediately, that header is usually why.&lt;/p&gt;

&lt;p&gt;For quick inspection without spinning up &lt;code&gt;kafka-avro-console-consumer&lt;/code&gt;, I use these browser tools (client-side only, nothing uploaded):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://json2class.com/en/avro-decode/" rel="noopener noreferrer"&gt;Avro decoder&lt;/a&gt; — paste base64/hex bytes + the schema, get JSON back&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://json2class.com/en/avro-schema-generator/" rel="noopener noreferrer"&gt;Avro schema generator&lt;/a&gt; — paste a sample JSON message, get a starting-point schema with proper null unions&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://json2class.com/en/avro-message-generator/" rel="noopener noreferrer"&gt;Avro message generator&lt;/a&gt; — generate test payloads from a schema&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Optional field = &lt;code&gt;["null", "type"]&lt;/code&gt;, null first, default null.&lt;/li&gt;
&lt;li&gt;New fields need defaults; never rename, alias instead.&lt;/li&gt;
&lt;li&gt;Defaults serve schema evolution, not producer laziness.&lt;/li&gt;
&lt;li&gt;Decoder failing on byte 0? Strip the 5-byte Confluent header.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kafka</category>
      <category>avro</category>
      <category>dataengineering</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to verify a file checksum on macOS, Linux, and Windows (and why you should)</title>
      <dc:creator>yuus_company</dc:creator>
      <pubDate>Sun, 06 Sep 2026 03:28:01 +0000</pubDate>
      <link>https://dev.to/yuus_company/how-to-verify-a-file-checksum-on-macos-linux-and-windows-and-why-you-should-4mnm</link>
      <guid>https://dev.to/yuus_company/how-to-verify-a-file-checksum-on-macos-linux-and-windows-and-why-you-should-4mnm</guid>
      <description>&lt;p&gt;You download an installer, a firmware image, or a database dump. Next to the download link the site lists something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SHA-256: 3a7bd3e2360a3d29eea436fcfb7e44c735d117c42d1c1835420b6b9942dd4f1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most people ignore it. Here's why you shouldn't, and the fastest way to check it on every OS.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a checksum actually proves
&lt;/h2&gt;

&lt;p&gt;A cryptographic hash (SHA-256, MD5, ...) is a fingerprint of a file's exact bytes. If even one bit changes — a corrupted download, a truncated transfer, or a tampered binary — the hash changes completely.&lt;/p&gt;

&lt;p&gt;Comparing your local hash against the published one proves two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Integrity&lt;/strong&gt; — the file wasn't corrupted in transit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authenticity&lt;/strong&gt; (partially) — the file is the one the publisher hashed. (Full authenticity needs a signature, but a checksum from a trusted HTTPS page is a solid baseline.)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The commands, per OS
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;macOS / Linux:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 file.zip     &lt;span class="c"&gt;# works on both&lt;/span&gt;
&lt;span class="nb"&gt;sha256sum &lt;/span&gt;file.zip         &lt;span class="c"&gt;# Linux (coreutils)&lt;/span&gt;
md5 file.zip               &lt;span class="c"&gt;# macOS MD5&lt;/span&gt;
&lt;span class="nb"&gt;md5sum &lt;/span&gt;file.zip            &lt;span class="c"&gt;# Linux MD5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (cmd):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="nb"&gt;certutil&lt;/span&gt; &lt;span class="na"&gt;-hashfile &lt;/span&gt;&lt;span class="kd"&gt;file&lt;/span&gt;.zip &lt;span class="kd"&gt;SHA256&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows (PowerShell):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-FileHash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file.zip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Algorithm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SHA256&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then eyeball-compare the output against the published value — or pipe it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"3a7bd3e2...  file.zip"&lt;/span&gt; | shasum &lt;span class="nt"&gt;-a&lt;/span&gt; 256 &lt;span class="nt"&gt;-c&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Which algorithm should you trust?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MD5&lt;/td&gt;
&lt;td&gt;Fine for duplicate detection and cache keys. &lt;strong&gt;Not&lt;/strong&gt; for security — collisions are practical.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-1&lt;/td&gt;
&lt;td&gt;Legacy compatibility only.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-256&lt;/td&gt;
&lt;td&gt;The default. Use this.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SHA-512&lt;/td&gt;
&lt;td&gt;Also fine; sometimes faster than SHA-256 on 64-bit CPUs.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The rule of thumb: if an attacker could benefit from forging a matching file, MD5 and SHA-1 are out.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you don't have a terminal handy
&lt;/h2&gt;

&lt;p&gt;Sometimes you're on a locked-down machine, or you just want to check a file quickly without remembering flags. Browser-based tools can hash files locally with the Web Crypto API — the file never leaves your machine.&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://json2class.com/en/file-hash/" rel="noopener noreferrer"&gt;this file hash tool&lt;/a&gt;: drag the file in, paste the expected checksum, and it auto-detects which algorithm matches. It streams large files in 8MB chunks, so multi-GB files work without freezing the tab.&lt;/p&gt;

&lt;p&gt;Whatever tool you use, verify that it computes hashes &lt;strong&gt;client-side&lt;/strong&gt; — uploading a sensitive file to hash it defeats the purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Always check the checksum for installers, firmware, and backups.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shasum -a 256&lt;/code&gt; / &lt;code&gt;certutil -hashfile ... SHA256&lt;/code&gt; / &lt;code&gt;Get-FileHash&lt;/code&gt; cover every OS.&lt;/li&gt;
&lt;li&gt;Prefer SHA-256. Keep MD5 for non-adversarial jobs.&lt;/li&gt;
&lt;li&gt;Browser tools work too, as long as hashing happens locally.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>tutorial</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
