<?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: Nikunj</title>
    <description>The latest articles on DEV Community by Nikunj (@nikunjcyber).</description>
    <link>https://dev.to/nikunjcyber</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%2F4024677%2F067e9ae2-7f7b-4bea-8a57-e792b13b6650.png</url>
      <title>DEV Community: Nikunj</title>
      <link>https://dev.to/nikunjcyber</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nikunjcyber"/>
    <language>en</language>
    <item>
      <title>## Building a File Encryption Tool in Python: What I Learned From My Own Bugs</title>
      <dc:creator>Nikunj</dc:creator>
      <pubDate>Sat, 11 Jul 2026 04:22:23 +0000</pubDate>
      <link>https://dev.to/nikunjcyber/-building-a-file-encryption-tool-in-python-what-i-learned-from-my-own-bugs-4jbe</link>
      <guid>https://dev.to/nikunjcyber/-building-a-file-encryption-tool-in-python-what-i-learned-from-my-own-bugs-4jbe</guid>
      <description>&lt;p&gt;As someone getting into cybersecurity, I wanted my first real project to be something more than a tutorial follow-along — I wanted to actually understand every line I wrote, not just make something that runs. So I built a command-line File Encryption Tool in Python, and along the way I hit (and fixed) a handful of bugs that taught me more than getting it right the first time ever would have.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/Nikunj-cyber/File-Encryption-Tool" rel="noopener noreferrer"&gt;github.com/Nikunj-cyber/File-Encryption-Tool&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What it does
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Generates a secure encryption key on first run, and reuses it on every run after&lt;/li&gt;
&lt;li&gt;Encrypts any file — text, images, PDFs, anything — into unreadable ciphertext&lt;/li&gt;
&lt;li&gt;Decrypts it back to the exact original bytes&lt;/li&gt;
&lt;li&gt;Keeps the encryption key out of Git entirely, so it never ends up on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python 3&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cryptography&lt;/code&gt;&lt;/strong&gt; library — specifically &lt;code&gt;Fernet&lt;/code&gt;, which combines AES-128 encryption with HMAC message authentication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Git/GitHub&lt;/strong&gt; for version control&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How it's structured
&lt;/h3&gt;

&lt;p&gt;Rather than one big script, I split it into small, single-responsibility modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;main.py          → orchestrates the program
key_manager.py   → generates, saves, and loads the encryption key
encrypt.py       → reads a file and encrypts it
decrypt.py       → reverses the process
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.py&lt;/code&gt; checks whether a key already exists. If not, it generates one and saves it; if it does, it just loads it. Either way, it then calls &lt;code&gt;encrypt_file()&lt;/code&gt; or &lt;code&gt;decrypt_file()&lt;/code&gt;, which stay completely unaware of &lt;em&gt;how&lt;/em&gt; the key came to exist — they just trust it's there. That separation turned out to be a genuinely useful lesson in its own right (more below).&lt;/p&gt;

&lt;h3&gt;
  
  
  The bugs that actually taught me something
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. A circular import, right at the start.&lt;/strong&gt;&lt;br&gt;
I initially put &lt;code&gt;from key_manager import generate_key, save_key, load_key&lt;/code&gt; &lt;em&gt;inside&lt;/em&gt; &lt;code&gt;key_manager.py&lt;/code&gt; itself instead of &lt;code&gt;main.py&lt;/code&gt; — meaning the file was trying to import itself, forever. First real lesson in &lt;em&gt;where&lt;/em&gt; imports actually belong and why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. An inverted &lt;code&gt;if/else&lt;/code&gt; that would've silently destroyed my key.&lt;/strong&gt;&lt;br&gt;
At one point my logic read:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keys/secret.key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generate_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;save_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that literally: &lt;em&gt;if the key is missing, load it&lt;/em&gt; (impossible — nothing to load), &lt;em&gt;if it exists, generate and overwrite it&lt;/em&gt; (meaning every single run after the first would silently destroy the previous key and make any already-encrypted file permanently unrecoverable). This one scared me a little once I understood what it actually meant — no error, no crash, just quietly wrong, forever. Fixing it taught me to read conditionals word-by-word instead of skimming them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Forgetting that &lt;code&gt;return&lt;/code&gt; exits a function immediately.&lt;/strong&gt;&lt;br&gt;
I tried adding a &lt;code&gt;print()&lt;/code&gt; confirmation &lt;em&gt;after&lt;/em&gt; a &lt;code&gt;return&lt;/code&gt; statement, not realizing that code is permanently unreachable — Python leaves the function the instant it hits &lt;code&gt;return&lt;/code&gt;. Small thing, but it's the kind of bug that's invisible until you actually understand &lt;em&gt;why&lt;/em&gt; it's invisible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The "did I actually save the file?" trap — twice.&lt;/strong&gt;&lt;br&gt;
I ran into &lt;code&gt;ModuleNotFoundError&lt;/code&gt; and &lt;code&gt;FileNotFoundError&lt;/code&gt; messages that referenced code I'd already fixed in my editor. Turned out I'd been running &lt;code&gt;python main.py&lt;/code&gt; without saving first, so Python was executing a stale version from disk. Now I check for the unsaved-file dot in VS Code before running anything, every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  A design decision I'd actually defend in an interview
&lt;/h3&gt;

&lt;p&gt;Early on I had to decide: should encryption overwrite the original file, or write a new one? I went with &lt;strong&gt;always writing a new file&lt;/strong&gt; (&lt;code&gt;file.txt&lt;/code&gt; → &lt;code&gt;file.txt.encrypted&lt;/code&gt;), never touching the original. The reasoning: if the program crashes mid-write, an in-place overwrite could leave you with a half-written, corrupted file — and the original would already be gone. Writing to a new file means the original stays untouched no matter what goes wrong. Small decision, but it's the kind of "what happens when this fails halfway" thinking that actually matters in security-adjacent tooling.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Handling &lt;code&gt;InvalidToken&lt;/code&gt; gracefully when someone tries decrypting with the wrong key or a corrupted file&lt;/li&gt;
&lt;li&gt;Accepting the target file as a command-line argument instead of a hardcoded path&lt;/li&gt;
&lt;li&gt;Possibly adding password-derived keys (PBKDF2) as an alternative to the random key, for portability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're also early in a cybersecurity/dev journey — I'd rather ship something small and actually understand every line than copy-paste something bigger. Happy to answer questions about any part of this if it's useful to you.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>cybersecurity</category>
      <category>python</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
