FILE SHARING

Sharing Files

Share encrypted files securely without exposing your keys. Control access with expiration dates and download limits.

~2 min read

Sharing encrypted files presents a challenge: how do you give someone access without giving them your encryption key? Helix solves this with secure share links that can include time-limited decryption capabilities.

Sharing Options

Public Link

For unencrypted files. Anyone with the link can download directly from Arweave.

Encrypted Share

Share encrypted files by including a re-encrypted key in the share link.

Time-Limited

Set an expiration date. After expiry, the share link stops working.

Download Limits

Limit the number of downloads. After the limit, the link is disabled.

How It Works

Unencrypted Files

For files uploaded without encryption, sharing is simple—the share link points directly to the Arweave content:

// Direct Arweave link
https://arweave.net/abc123xyz...

// Or through Helix for tracking
https://helix.storage/share/clx1234...
  → Redirects to arweave.net/abc123xyz...
  → Increments download counter

Encrypted Files

Sharing encrypted files requires securely transmitting the decryption key. Here's how we handle it:

Owner Creates Share Link

  1. Owner's encryption key (K1)
  2. Generate share password (P)
  3. Derive share key from password: K2 = PBKDF2(P)
  4. Encrypt K1 with K2: encrypted_key = AES(K1, K2)
  5. Store encrypted_key in database
  6. Share URL includes password in fragment

→ helix.storage/share/abc123#password_here

Recipient Accesses File

  1. Extract password from URL fragment (never sent to server)
  2. Request encrypted_key from server
  3. Derive K2 from password: K2 = PBKDF2(P)
  4. Decrypt: K1 = AES_decrypt(encrypted_key, K2)
  5. Fetch encrypted file from Arweave
  6. Decrypt file with K1

Fragment Privacy

The password is in the URL fragment (after #). Fragments are never sent to servers—they stay in the browser. This means we never see the password needed to decrypt the share key.

Creating Share Links

create-share.ts
// Generate a secure share link for an encrypted file
async function createEncryptedShare(
  fileId: string,
  encryptionKey: CryptoKey,
  options: ShareOptions
) {
  // Generate random password for URL
  const password = generateSecurePassword(32);

  // Derive key from password
  const salt = crypto.getRandomValues(new Uint8Array(16));
  const shareKey = await deriveKey(password, salt);

  // Encrypt the file's encryption key
  const exportedKey = await crypto.subtle.exportKey('raw', encryptionKey);
  const { encrypted, iv } = await encrypt(exportedKey, shareKey);

  // Create share in database
  const share = await fetch('/api/share', {
    method: 'POST',
    body: JSON.stringify({
      fileId,
      encryptedKey: packKeyData(encrypted, iv, salt),
      expiresAt: options.expiresAt,
      maxDownloads: options.maxDownloads,
    }),
  });

  const { id } = await share.json();

  // Return URL with password in fragment
  return `https://helix.storage/share/${id}#${password}`;
}

function generateSecurePassword(length: number): string {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  const array = crypto.getRandomValues(new Uint8Array(length));
  return Array.from(array, b => chars[b % chars.length]).join('');
}

Access Controls

Share links support multiple access control mechanisms:

ControlDescriptionUse Case
Expiration DateLink stops working after specified timeTime-sensitive documents
Download LimitMaximum number of downloads allowedPrevent unlimited redistribution
PasswordBuilt into URL fragment for encrypted filesSecure key transmission
share-options.ts
interface ShareOptions {
  // Expiration (optional)
  expiresAt?: Date;

  // Download limit (optional)
  maxDownloads?: number;
}

// Examples
const shareOptions = {
  // Expires in 7 days
  expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),

  // Allow only 5 downloads
  maxDownloads: 5,
};

Checking Access

When someone accesses a share link, we verify:

verify-share-access.ts
async function verifyShareAccess(shareId: string) {
  const share = await prisma.shareLink.findUnique({
    where: { id: shareId },
    include: { file: true },
  });

  if (!share) {
    throw new Error('Share link not found');
  }

  // Check expiration
  if (share.expiresAt && share.expiresAt < new Date()) {
    throw new Error('Share link has expired');
  }

  // Check download limit
  if (share.maxDownloads && share.downloadCount >= share.maxDownloads) {
    throw new Error('Download limit reached');
  }

  // Increment download count
  await prisma.shareLink.update({
    where: { id: shareId },
    data: { downloadCount: { increment: 1 } },
  });

  return share;
}

Revoking Access

You can revoke share links at any time:

revoke-share.ts
// Delete the share link
await fetch(`/api/share/${shareId}`, {
  method: 'DELETE',
  headers: {
    Authorization: `Bearer ${token}`,
  },
});

// The file remains on Arweave, but the share link
// no longer resolves. Recipients who saved the
// encrypted key locally may still access the file.

Revocation Limits

Once someone has downloaded an encrypted file and extracted the key, revoking the share link won't prevent them from accessing the Arweave data directly. Revocation prevents new access, not existing access.

Share Link Anatomy

SHARE URL STRUCTURE

https://helix.storage/share/clx1234abc#Kj8mNp2qRs5tUv7w

Domain

helix.storage

Share ID

Identifies the share link

Password

Never sent to server

For unencrypted files, no fragment is needed: helix.storage/share/clx1234abc

Sharing Best Practices

Use Expiration for Sensitive Files

Always set expiration dates for sensitive documents. A week is usually plenty—recipients can download and save locally.

Set Download Limits

If sharing with one person, set maxDownloads to 1-2. This prevents accidental forwarding of the link.

Share Full URL

Always share the complete URL including the fragment. If truncated, recipients won't be able to decrypt.

Verify Before Sharing

Test share links before sending. Open in an incognito window to confirm recipients will see what you expect.

Secure Channels

The share URL contains the decryption key. Share it through secure channels—encrypted messaging apps, not public posts.

Last updated: February 2025