DEV Community

Ajay Mourya
Ajay Mourya

Posted on

Securing ShareText: Moving from Client-Side Obfuscation to Server-Side AES-GCM

Summer Bug Smash: Clear the Lineup 🐛🛹

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.

Project Overview

ShareText is a Spring Boot application for securely sharing text through unique links.


Users can:

  • Create shareable text links
  • Protect links with passwords
  • Set expiration dates
  • Use custom aliases
  • Retrieve shared content through a web interface


The project stores shared content in MySQL and provides a frontend using HTML, CSS, and JavaScript.

Bug Fix or Performance Improvement

The original encryption implementation exposed its AES key directly in the frontend JavaScript:


const keyBytes = new Uint8Array([
    11, 22, 33, 44, 55, 66, 77, 88,
    99, 10, 11, 12, 13, 14, 15, 16,
    17, 18, 19, 20, 21, 22, 23, 24,
    25, 26, 27, 28, 29, 30, 31, 32
]);
Enter fullscreen mode Exit fullscreen mode

This meant that anyone could inspect the website source code and recover the encryption key.

The application also used AES-CBC without authenticated integrity protection. This could allow encrypted data to be modified without reliable tamper detection.

The main security issues were:

  • Encryption key exposed to every client
  • Client-side encryption providing no real secret protection
  • Shared content stored as plaintext in the database
  • AES-CBC used without authentication
  • No server-side key validation
  • Passwords and content handled through the same exposed encryption model

Code

The main implementation changes are available in:

  • PayloadCrypto.java
  • SharedTextService.java
  • SharedTextController.java
  • app.js
  • application.properties

PR: https://github.com/ajaym0urya/ShareText/pull/1
https://github.com/ajaym0urya/ShareText/commit/e41f95c05be0dc5a5ebd78f67c7aed7653b5dfec

My Improvements

I moved encryption responsibility from the browser to the server.

The frontend now sends normal JSON over HTTPS:

const payload = {
    content: textContent.value,
    expirationDate: calculateExpiry(expirationSelect.value),
    password: sharePassword.value.trim() || null,
    customAlias: customLinkAlias.value.trim() || null
};
Enter fullscreen mode Exit fullscreen mode

The browser no longer contains the encryption key or encryption logic.

On the server, ShareText now uses AES-256-GCM:

private static final String CIPHER = "AES/GCM/NoPadding";
private static final int KEY_SIZE_BYTES = 32;
private static final int NONCE_SIZE_BYTES = 12;
Enter fullscreen mode Exit fullscreen mode

A new random nonce is generated for every encryption operation. The stored value contains:

Base64(nonce + ciphertext + authentication tag)
Enter fullscreen mode Exit fullscreen mode

AES-GCM provides both confidentiality and integrity. If someone modifies the encrypted database value, authentication fails during decryption.

Content is encrypted before persistence:

text.setContent(payloadCrypto.encrypt(request.getContent()));
Enter fullscreen mode Exit fullscreen mode

Content is decrypted only after expiration and password checks succeed:

response.setContent(payloadCrypto.decrypt(text.getContent()));
Enter fullscreen mode Exit fullscreen mode

Passwords continue to use BCrypt hashing:

text.setPasswordHash(
    passwordEncoder.encode(request.getPassword())
);
Enter fullscreen mode Exit fullscreen mode

The encryption key is now loaded from an environment variable:

sharetext.encryption-key=${SHARETEXT_ENCRYPTION_KEY}
Enter fullscreen mode Exit fullscreen mode

For local testing, I generate a random 32-byte key in PowerShell:

$key = New-Object byte[] 32

$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
$rng.GetBytes($key)
$rng.Dispose()

$env:SHARETEXT_ENCRYPTION_KEY =
    [Convert]::ToBase64String($key)

mvn spring-boot:run
Enter fullscreen mode Exit fullscreen mode

This fix provides:

  • HTTPS transport protection
  • AES-256-GCM encryption at rest
  • Random nonce generation
  • Tamper detection
  • Server-only encryption keys
  • BCrypt password hashing
  • No cryptographic secrets in frontend JavaScript

This is server-side encryption rather than strict zero-knowledge end-to-end encryption because the server decrypts content before returning it to an authorized user.

Thank you for reading my post.

Top comments (0)