Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
Free 30-min Web3 Consultation
Book Now
Smart Contract Security Audits
Learn More
Custom DeFi Protocol Development
Explore
Full-Stack Web3 dApp Development
View Services
LABS
Guides

Setting Up Encryption for User Data

A practical guide for developers implementing encryption to protect user data in decentralized applications, covering library selection, key management, and production patterns.
Chainscore © 2026
introduction
PRIVACY FUNDAMENTALS

Introduction to User Data Encryption in Web3

A guide to implementing encryption for user data in decentralized applications, moving beyond on-chain transparency.

In Web3, the default state of public blockchain data is transparency. Every transaction, smart contract interaction, and piece of data stored on-chain is visible to anyone. While this enables trustless verification, it creates a significant privacy challenge for user-centric applications. Sensitive information like personal identifiers, private messages, or health data cannot be stored in plaintext on a public ledger. User data encryption is the foundational technique for building private, compliant, and user-sovereign dApps that leverage blockchain's security without sacrificing confidentiality.

The core principle is client-side encryption. Data is encrypted on the user's device before it is sent to a storage layer, ensuring only the intended recipient(s) can decrypt it. This uses asymmetric cryptography, typically via a keypair: a public key for encryption and a private key for decryption. Popular libraries like libsodium (via libsodium-wrappers) or the Web Crypto API provide these primitives. For example, to encrypt a message for another user, your dApp would use their public key, and they would decrypt it with their private key, which never leaves their wallet or secure enclave.

A common pattern combines on-chain and off-chain storage. Encryption keys or access controls can be stored or referenced on-chain (e.g., in a smart contract), while the bulky encrypted data itself is stored off-chain on decentralized storage networks like IPFS or Arweave. The on-chain record might contain a Content Identifier (CID) pointing to the encrypted data on IPFS and the public key of the authorized decryptor. This keeps storage costs low while maintaining a verifiable, permissionless link to the data's location and access rules.

Implementing this requires careful key management. Users' private keys are their ultimate source of access. Losing a wallet means losing data forever, as there is no central recovery service. Solutions involve social recovery (via smart contract guardians) or deriving encryption keys from a wallet signature using protocols like ECDH (Elliptic Curve Diffie-Hellman). For example, two parties can generate a shared secret from their keypairs to encrypt data only they can access, a method used by secure messaging protocols.

Frameworks and SDKs simplify development. Lit Protocol enables programmable encryption where decryption is gated by on-chain conditions (e.g., holding an NFT). X25519 is a standard curve for efficient key agreement. A basic encryption flow in JavaScript using the Web Crypto API involves generating a symmetric key, encrypting data with it, then encrypting that key with the recipient's public key (hybrid encryption). The final payload sent to storage includes the encrypted data and the encrypted symmetric key.

This approach unlocks use cases like private decentralized identity (Verifiable Credentials), confidential DAO voting, encrypted medical records, and secure enterprise data sharing. By mastering user data encryption, developers can build the next generation of Web3 applications that respect user privacy by default, shifting the paradigm from "everything is public" to "everything is permissioned."

prerequisites
PREREQUISITES AND SETUP

Setting Up Encryption for User Data

This guide covers the essential steps and cryptographic libraries required to implement secure, on-chain encryption for user data in Web3 applications.

Before implementing encryption, you need to understand the core cryptographic primitives. For Web3 applications, asymmetric encryption is typically used, where a user's public key encrypts data and their private key decrypts it. The most common standard is AES-GCM (Advanced Encryption Standard in Galois/Counter Mode) for symmetric encryption of the data payload, combined with RSA-OAEP or Elliptic Curve Integrated Encryption Scheme (ECIES) for securely sharing the symmetric key. Libraries like libsodium (via sodium-plus) or the native Web Crypto API provide robust, audited implementations of these algorithms, which are preferable to writing your own cryptographic code.

Your development environment must be configured to handle these operations. For a Node.js backend, install packages like @stablelib or tweetnacl. In a browser context, ensure your application is served over HTTPS to enable the secure context required by the Web Crypto API. You'll also need a method to manage key pairs. For blockchain integration, you can derive an encryption key pair from a user's wallet signature using a Key Derivation Function (KDF) like HKDF, or generate a separate key pair stored in the browser's IndexedDB via the CryptoKey interface. This separation ensures that a transaction signing key is not directly used for encryption, adhering to security best practices.

A typical implementation flow involves three steps. First, generate a random symmetric key (the content encryption key) to encrypt the user's data string or file using AES-GCM, which provides both confidentiality and integrity. Second, encrypt this symmetric key with the recipient's public key using RSA-OAEP or ECIES. Finally, the encrypted data and the encrypted symmetric key can be bundled and stored on-chain or in a decentralized storage network like IPFS or Arweave. Only the intended recipient, holding the corresponding private key, can decrypt the symmetric key and subsequently the original data. Always include authenticated data in your AES-GCM encryption to bind the ciphertext to its context, preventing substitution attacks.

key-concepts
USER DATA SECURITY

Core Cryptographic Concepts

Essential cryptographic primitives for securing user data on-chain and off-chain, from key management to data privacy.

ON-CHAIN VS. OFF-CHAIN

Encryption Method Comparison

Comparison of common encryption approaches for securing user data in Web3 applications.

FeatureOn-Chain EncryptionOff-Chain Encryption (IPFS/Storage)Hybrid Approach (ZK-Proofs)

Data Visibility on Blockchain

Encrypted data is public

Only content hash is public

Only validity proof is public

Client-Side Key Management

Compute Overhead for User

Low

Low

High (proof generation)

Gas Cost for Storage

High (per byte)

Low (single hash)

Medium (proof + hash)

Data Mutability

Conditional (via proofs)

Suitable for Private NFTs

Example Protocol

Ethereum (encrypted calldata)

Ceramic Network, IPFS Private

Aztec, zkSync

symmetric-encryption-implementation
SECURITY

Implementing Symmetric Encryption (AES-GCM)

A practical guide to using the AES-GCM algorithm for encrypting sensitive user data in Web3 applications, with a focus on key derivation and secure implementation.

Symmetric encryption uses a single secret key for both encrypting and decrypting data. The Advanced Encryption Standard (AES) is the most widely adopted symmetric cipher, and Galois/Counter Mode (GCM) is its recommended mode of operation. AES-GCM provides both confidentiality (via encryption) and authenticity (via an authentication tag), protecting data from being read or tampered with. For Web3 applications, this is crucial for securing off-chain user data like profile information, private messages, or encrypted notes before storage on platforms like IPFS or centralized databases.

The cornerstone of security is the encryption key. You should never use a user's password directly as the key. Instead, derive a cryptographically strong key using a Key Derivation Function (KDF) like PBKDF2, Scrypt, or Argon2. These functions intentionally slow down the key generation process, making brute-force attacks impractical. For example, using PBKDF2 with a high iteration count (e.g., 600,000) and a unique, random salt for each user ensures that even identical passwords produce different keys. The salt is not secret and can be stored alongside the encrypted data.

Here is a basic implementation flow in Node.js using the crypto module. First, derive a key from a password and salt. Then, use that key to encrypt data with AES-256-GCM, which generates a unique initialization vector (IV) and an authentication tag.

javascript
const crypto = require('crypto');

function encryptData(plaintext, password) {
  const salt = crypto.randomBytes(16);
  const key = crypto.scryptSync(password, salt, 32); // 32 bytes = 256-bit key
  
  const iv = crypto.randomBytes(12); // Recommended IV size for GCM
  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
  
  let encrypted = cipher.update(plaintext, 'utf8', 'hex');
  encrypted += cipher.final('hex');
  const authTag = cipher.getAuthTag();
  
  // Return IV, authTag, salt, and ciphertext for storage
  return {
    iv: iv.toString('hex'),
    authTag: authTag.toString('hex'),
    salt: salt.toString('hex'),
    encryptedData: encrypted
  };
}

To decrypt, you must have all components: the derived key (requiring the original password and salt), the IV, the authentication tag, and the ciphertext. The decryption process will fail if the authentication tag is invalid, indicating the data has been corrupted or tampered with. This built-in integrity check is a key advantage of GCM over older modes like CBC. Always use authenticated encryption modes to prevent padding oracle attacks and other cryptographic vulnerabilities.

For Web3 integration, consider where to store each piece of the encryption puzzle. The encrypted data can be stored on decentralized storage like IPFS or Arweave. The salt and IV are not secret and can be stored alongside it or in a smart contract. However, the key (derived from the user's password or seed phrase) must never be stored. This creates a user-centric security model where only the individual with the secret can access their data, aligning with Web3's ethos of self-sovereignty. For key management, consider using MetaMask's eth_decrypt/eth_getEncryptionPublicKey for asymmetric encryption between users, but use symmetric AES-GCM for encrypting a user's own data.

Common pitfalls include using a static IV (which completely breaks security), failing to verify the authentication tag on decryption, or using an insufficient KDF iteration count. Libraries like libsodium (via sodium-native or libsodium-wrappers) offer even more robust, hard-to-misuse APIs for these operations. Always follow the principle of least privilege and encrypt only the necessary fields. For on-chain data, remember that true privacy requires zero-knowledge proofs or fully homomorphic encryption, as symmetric encryption is not suitable for public, verifiable computation.

asymmetric-encryption-implementation
SECURE DATA STORAGE

Implementing Asymmetric Encryption (libsodium)

A practical guide to using libsodium's public-key cryptography for encrypting user data in Web3 applications, ensuring end-to-end security.

Asymmetric encryption, or public-key cryptography, is essential for secure communication and data storage where parties don't share a secret key. In a typical Web3 scenario, a user's public key can be used to encrypt data that only their corresponding private key can decrypt. This is crucial for securing private messages, encrypting sensitive user data before on-chain storage, or implementing secure key management systems. The libsodium library provides a modern, high-level API for these operations, abstracting away complex cryptographic details and mitigating common implementation pitfalls.

To begin, install libsodium. For Node.js, use npm install sodium-native. For browsers or React Native, libsodium-wrappers is recommended. The core operations involve key pair generation, encryption, and decryption. A key pair consists of a public key (for encryption) and a secret key (for decryption), which are 32-byte buffers. Libsodium uses the X25519 elliptic curve for key exchange and the XSalsa20 stream cipher with Poly1305 MAC for authenticated encryption in its crypto_box API, ensuring both confidentiality and integrity.

Here is a basic workflow for encrypting a message. First, generate a key pair for both the sender and receiver. The sender encrypts a message using the receiver's public key and their own secret key. This creates a nonce (number used once) and a ciphertext. The code example below demonstrates this process in Node.js with sodium-native.

javascript
const sodium = require('sodium-native');
// Generate key pairs
let alicePublicKey = Buffer.alloc(sodium.crypto_box_PUBLICKEYBYTES);
let aliceSecretKey = Buffer.alloc(sodium.crypto_box_SECRETKEYBYTES);
sodium.crypto_box_keypair(alicePublicKey, aliceSecretKey);
// ... generate bob's key pair similarly
// Encrypt a message from Alice to Bob
let message = Buffer.from('Sensitive user data');
let ciphertext = Buffer.alloc(message.length + sodium.crypto_box_MACBYTES);
let nonce = Buffer.alloc(sodium.crypto_box_NONCEBYTES);
sodium.randombytes_buf(nonce);
sodium.crypto_box_easy(ciphertext, message, nonce, bobPublicKey, aliceSecretKey);
// Send nonce and ciphertext to Bob

Decryption is the reverse process. The receiver (Bob) uses their own secret key, the sender's (Alice's) public key, the nonce, and the ciphertext to retrieve the original message. It's critical to never reuse a nonce with the same key pair, as this completely breaks security. Libsodium's crypto_box_open_easy function handles verification of the authentication tag. If the ciphertext was tampered with or the wrong keys are used, this function will return false or throw an error, preventing the use of corrupted data.

For encrypting data intended for storage (e.g., in a smart contract or database), consider the sealed box API (crypto_box_seal). This method only requires the recipient's public key for encryption, not the sender's secret key. This is ideal for scenarios where the encrypting party is not a permanent participant, such as a dApp frontend encrypting data for a user's wallet. The decryption still requires the recipient's secret key. This pattern is commonly used in decentralized storage solutions or for private data fields in smart contracts.

When implementing this in production, key management is paramount. Never store plaintext secret keys on a server. For browser environments, consider using the Web Crypto API or libsodium-wrappers in a Web Worker. For user-facing applications, the secret key is often derived from the user's wallet signature or stored in secure, client-side storage. Always refer to the official libsodium documentation for the latest security recommendations and API details for your specific platform.

key-management-patterns
KEY MANAGEMENT AND STORAGE PATTERNS

Setting Up Encryption for User Data

A guide to implementing robust encryption for user data in Web3 applications, covering key derivation, encryption algorithms, and secure storage patterns.

User data encryption is a critical security layer for any application handling sensitive information, from private messages to personal identifiers. In Web3, where data sovereignty is a core principle, client-side encryption ensures that data is encrypted before it leaves the user's device, preventing the service provider from accessing plaintext. The process typically involves deriving a strong encryption key from a user secret, such as a password or a wallet signature, and using a standard algorithm like AES-256-GCM to encrypt the data. This pattern shifts the trust model, making the user the sole custodian of their data's accessibility.

The foundation of secure encryption is a strong key. For password-based systems, use a key derivation function (KDF) like scrypt or Argon2id to transform a user's password into a cryptographic key. These functions are computationally intensive, making brute-force attacks impractical. For wallet-based authentication, you can derive a key from a signature of a known message using the user's private key, a method often used in decentralized storage systems. Never use passwords or raw private keys directly as encryption keys. The derived key should then be used with a symmetric cipher, where AES-256 in Galois/Counter Mode (GCM) is the industry standard, providing both confidentiality and integrity verification.

Once data is encrypted, you must decide where to store the ciphertext. Common patterns include on-chain storage (expensive, for small critical data), decentralized storage networks like IPFS or Arweave (for permanence), or traditional cloud storage. The encryption key itself must be managed carefully: it should never be stored alongside the data it protects. For recovery, consider social recovery schemes, shamir's secret sharing, or multi-party computation (MPC) to split the key. Always include metadata like the encryption algorithm version, initialization vector (IV), and authentication tag with the ciphertext to ensure it can be decrypted correctly in the future.

Here is a practical example using the Web Crypto API in a browser environment to encrypt a user's JSON data with a password-derived key:

javascript
async function encryptData(password, data) {
  // 1. Create a salt
  const salt = crypto.getRandomValues(new Uint8Array(16));
  // 2. Derive a key from the password using PBKDF2
  const baseKey = await crypto.subtle.importKey(
    'raw',
    new TextEncoder().encode(password),
    'PBKDF2',
    false,
    ['deriveKey']
  );
  const key = await crypto.subtle.deriveKey(
    { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' },
    baseKey,
    { name: 'AES-GCM', length: 256 },
    false,
    ['encrypt', 'decrypt']
  );
  // 3. Encrypt the data
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const ciphertext = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv: iv },
    key,
    new TextEncoder().encode(JSON.stringify(data))
  );
  // 4. Return the packaged ciphertext, IV, and salt
  return { ciphertext, iv, salt };
}

This function outputs an object containing the encrypted data and the necessary parameters (IV, salt) for decryption, which can then be stored.

Implementing encryption introduces complexity, particularly around key loss and versioning. Always plan for key rotation procedures to respond to potential compromises. Use authenticated encryption modes like AES-GCM to detect tampering. For production systems, consider audited libraries such as libsodium or framework-specific tools. The goal is to create a system where a data breach of your storage backend results in attackers obtaining only useless encrypted blobs, fundamentally protecting user privacy and aligning with the decentralized ethos of user-owned data.

ENCRYPTION

Common Implementation Mistakes

Implementing encryption for user data on-chain requires careful consideration of key management, gas costs, and data visibility. These are the most frequent pitfalls developers encounter.

A common misconception is that encryption alone guarantees privacy. If you store the decryption key on-chain (e.g., in a public variable or event log), the data is effectively public. The blockchain's transparency means any data you write is permanently visible.

Key Mistake: Publishing the key alongside the ciphertext. Solution: Use a client-side or off-chain key management system. The smart contract should only ever handle ciphertext. For true privacy, consider using zero-knowledge proofs (like zk-SNARKs) with circuits such as those from Circom or Halo2 to prove knowledge of data without revealing it.

USER DATA ENCRYPTION

Frequently Asked Questions

Common questions and solutions for implementing encryption in Web3 applications, covering key management, on-chain considerations, and best practices.

Symmetric encryption uses a single shared key for both encryption and decryption, while asymmetric encryption uses a public/private key pair.

Symmetric encryption (e.g., AES-256-GCM) is fast and efficient for encrypting large amounts of data. The challenge is securely sharing the key. In Web3, this key is often encrypted with the user's public key and stored alongside the ciphertext.

Asymmetric encryption (e.g., RSA, ECIES) allows anyone to encrypt data with a public key, but only the private key holder can decrypt it. It's ideal for secure key exchange and encrypting small data packets, but is computationally heavier for bulk data.

Most practical implementations use a hybrid approach: generate a random symmetric key to encrypt the user data, then encrypt that key with the user's asymmetric public key for secure storage and sharing.

conclusion
IMPLEMENTATION REVIEW

Conclusion and Next Steps

You have successfully implemented a robust encryption strategy for user data. This guide concludes with a summary of key concepts and practical steps for further development.

This guide covered the essential components of end-to-end encryption (E2EE) for user data. You learned to generate and manage cryptographic keys using libsodium or Web Crypto API, encrypt sensitive data at the application layer before storage, and ensure only authorized users can decrypt it. The core principle is that data is encrypted with a user-specific key derived from their credentials, meaning the plaintext is never exposed to your server's database. This architecture is fundamental for building trust in applications handling private messages, health records, or financial information.

For production systems, key management is your most critical operational concern. Consider these next steps: - Implement a secure, versioned key storage solution like HashiCorp Vault or AWS KMS. - Establish a key rotation policy to periodically update encryption keys and re-encrypt data. - Add audit logging for all encryption and decryption operations to monitor for anomalies. - Plan for data recovery by securely backing up and escrowing master keys, ensuring you can restore access if a user loses their password.

To deepen your understanding, explore these advanced topics and resources. Study zero-knowledge proofs (ZKPs) for scenarios where you must prove data validity without revealing the data itself, using libraries like snarkjs. Review the OWASP Cryptographic Storage Cheat Sheet for best practice guidelines. For decentralized applications, examine how protocols like Lit Protocol enable programmable encryption and access control. Finally, always use reputable, audited libraries and never roll your own cryptographic primitives.