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 counterEncrypted Files
Sharing encrypted files requires securely transmitting the decryption key. Here's how we handle it:
Owner Creates Share Link
- Owner's encryption key (K1)
- Generate share password (P)
- Derive share key from password: K2 = PBKDF2(P)
- Encrypt K1 with K2: encrypted_key = AES(K1, K2)
- Store encrypted_key in database
- Share URL includes password in fragment
→ helix.storage/share/abc123#password_here
Recipient Accesses File
- Extract password from URL fragment (never sent to server)
- Request encrypted_key from server
- Derive K2 from password: K2 = PBKDF2(P)
- Decrypt: K1 = AES_decrypt(encrypted_key, K2)
- Fetch encrypted file from Arweave
- Decrypt file with K1
Fragment Privacy
Creating Share Links
// 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:
| Control | Description | Use Case |
|---|---|---|
| Expiration Date | Link stops working after specified time | Time-sensitive documents |
| Download Limit | Maximum number of downloads allowed | Prevent unlimited redistribution |
| Password | Built into URL fragment for encrypted files | Secure key transmission |
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:
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:
// 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
Share Link Anatomy
SHARE URL STRUCTURE
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
Last updated: February 2025