Any developer can write their unique rules. This is FISE's superpower.
FISE doesn't require cryptography expertise. It doesn't need complex algorithms. It just needs you to write your unique offset function.
Traditional Encryption Libraries:
- ❌ Require cryptographic knowledge
- ❌ Fixed algorithms (AES, RSA, etc.)
- ❌ Same for everyone → universal decoders exist
- ❌ Complex key management
FISE Approach:
- ✅ Any developer can write rules
- ✅ Unique per app → no universal decoder
- ✅ Simple math → no crypto expertise needed
- ✅ No keys → rules-as-code, not secrets
FISE is incredibly simple - you only need to define 3 security points:
Rules definition (shared between backend and frontend):
// rules.ts - Shared between backend and frontend
import { defaultRules } from "fise";
// Just copy defaultRules and modify the offset!
export const myRules = {
...defaultRules,
offset(c, ctx) {
// Your unique offset - just change the multiplier/modulo!
const t = ctx.timestamp ?? 0;
return (c.length * 13 + (t % 17)) % c.length; // Different primes!
}
};Backend (encrypt):
// backend.ts
import { fiseEncrypt } from "fise";
import { myRules } from "./rules.js";
// Encrypt on backend before sending to frontend
const encrypted = fiseEncrypt("Hello, World!", myRules);
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
// (sample base64-encoded output - actual encrypted text will vary due to random salt)
// Send encrypted to frontend via API
res.json({ data: encrypted });Frontend (decrypt):
// frontend.ts
import { fiseDecrypt } from "fise";
import { myRules } from "./rules.js";
// Receive encrypted from API
const { data: encrypted } = await fetch('/api/data').then(r => r.json());
// Decrypt on frontend
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
const decrypted = fiseDecrypt(encrypted, myRules);
// decrypted: "Hello, World!" (decrypted plaintext)
console.log(decrypted); // "Hello, World!"Rules definition (shared between backend and frontend):
// rules.ts - Shared between backend and frontend
import { FiseBuilder } from "fise";
// Use a preset - one line!
export const rules = FiseBuilder.defaults().build();
// or
// export const rules = FiseBuilder.hex().build();
// or
// export const rules = FiseBuilder.timestamp(13, 17).build();Backend (encrypt):
// backend.ts
import { fiseEncrypt } from "fise";
import { rules } from "./rules.js";
const encrypted = fiseEncrypt("Hello, World!", rules);
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
// (sample base64-encoded output - actual encrypted text will vary due to random salt)
res.json({ data: encrypted });Frontend (decrypt):
// frontend.ts
import { fiseDecrypt } from "fise";
import { rules } from "./rules.js";
const { data: encrypted } = await fetch('/api/data').then(r => r.json());
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
const decrypted = fiseDecrypt(encrypted, rules);
// decrypted: "Hello, World!" (decrypted plaintext)Rules definition (shared between backend and frontend):
// rules.ts - Shared between backend and frontend
import { FiseRules } from "fise";
// Define your rules - just 3 methods!
export const myRules: FiseRules = {
// 1. Where to place metadata (varies per app - THIS IS THE KEY!)
offset(cipherText, ctx) {
const t = ctx.timestamp ?? 0;
return (cipherText.length * 7 + (t % 11)) % cipherText.length;
},
// 2. How to encode salt length (usually base36)
encodeLength(len) {
return len.toString(36).padStart(2, "0");
},
// 3. How to decode salt length (must match encodeLength)
decodeLength(encoded) {
return parseInt(encoded, 36);
}
};Backend (encrypt):
// backend.ts
import { fiseEncrypt } from "fise";
import { myRules } from "./rules.js";
// Encrypt on backend
const encrypted = fiseEncrypt("Hello, World!", myRules);
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
// (sample base64-encoded output - actual encrypted text will vary due to random salt)
res.json({ data: encrypted });Frontend (decrypt):
// frontend.ts
import { fiseDecrypt } from "fise";
import { myRules } from "./rules.js";
// Decrypt on frontend
const { data: encrypted } = await fetch('/api/data').then(r => r.json());
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
const decrypted = fiseDecrypt(encrypted, myRules);
// decrypted: "Hello, World!" (decrypted plaintext)
console.log(decrypted); // "Hello, World!"That's it! Everything else is automated with secure defaults.
-
offset()- PRIMARY SECURITY POINT- Calculates where to insert the encoded salt length in the ciphertext
- Must vary per app - this is what makes each deployment unique
- Creates spatial diversity
-
encodeLength()- Format diversity- Encodes the salt length as a string (e.g., base36, base62, hex)
- Creates format diversity - different apps use different encodings
-
decodeLength()- Extraction diversity- Decodes the encoded salt length back to a number
- Must match
encodeLength-decode(encode(len)) === len
- ✅ Salt extraction (default: tail-based)
- ✅ Brute-force search for salt length (default: 10-99)
- ✅ Metadata size inference
- ✅ All internal logic
The simplest approach - most developers just copy defaultRules and change the offset!
Rules definition (shared between backend and frontend):
// rules.ts - Shared between backend and frontend
import { defaultRules } from "fise";
// Just copy and modify - that's it!
export const myRules = {
...defaultRules,
offset(c, ctx) {
// Just change the multiplier/modulo - you're done!
const t = ctx.timestamp ?? 0;
return (c.length * 13 + (t % 17)) % c.length; // Different primes!
}
};Backend (encrypt):
// backend.ts
import { fiseEncrypt } from "fise";
import { myRules } from "./rules.js";
const plaintext = "Hello";
const encrypted = fiseEncrypt(plaintext, myRules);
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
// (sample base64-encoded output - actual encrypted text will vary due to random salt)
res.json({ data: encrypted });Frontend (decrypt):
// frontend.ts
import { fiseDecrypt } from "fise";
import { myRules } from "./rules.js";
const { data: encrypted } = await fetch('/api/data').then(r => r.json());
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
const decrypted = fiseDecrypt(encrypted, myRules);
// decrypted: "Hello" (decrypted plaintext)That's it! You now have unique rules. Everything else uses secure defaults.
Backend (encrypt):
// backend.ts
import { fiseEncrypt, defaultRules } from "fise";
const plaintext = "Hello";
const encrypted = fiseEncrypt(plaintext, defaultRules);
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
// (sample base64-encoded output - actual encrypted text will vary due to random salt)
res.json({ data: encrypted });Frontend (decrypt):
// frontend.ts
import { fiseDecrypt, defaultRules } from "fise";
const { data: encrypted } = await fetch('/api/data').then(r => r.json());
// encrypted: "22DD0WVDdpEiYqGgUWEg==DXz8XE2qEhir3KwoowSUnUA40rVIQbVT3FzgoZRBWExbu5D5Eg1dcTg2GkqvBnf6X3AZZKNoMy"
const decrypted = fiseDecrypt(encrypted, defaultRules);
// decrypted: "Hello" (decrypted plaintext)const rules = {
offset(c, ctx) {
// Your unique offset calculation
const t = ctx.timestamp ?? 0;
return (c.length * 13 + (t % 17)) % c.length;
},
encodeLength: (len) => len.toString(36).padStart(2, "0"),
decodeLength: (encoded) => parseInt(encoded, 36)
};const rules = {
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
return (c.length * 7 + (t % 11)) % c.length;
},
// Base62 encoding instead of base36
encodeLength(len) {
const base62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let result = "";
let n = len;
do {
result = base62[n % 62] + result;
n = Math.floor(n / 62);
} while (n > 0);
return result.padStart(2, "0");
},
decodeLength(encoded) {
const base62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let result = 0;
for (let i = 0; i < encoded.length; i++) {
result = result * 62 + base62.indexOf(encoded[i]);
}
return result;
}
};const rules = {
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
return (c.length * 7 + (t % 11)) % c.length;
},
encodeLength: (len) => len.toString(36).padStart(2, "0"),
decodeLength: (encoded) => parseInt(encoded, 36),
saltRange: { min: 20, max: 150 } // Wider range for more security
};const rules = {
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
return (c.length * 7 + (t % 11)) % c.length;
},
encodeLength: (len) => len.toString(36).padStart(2, "0"),
decodeLength: (encoded) => parseInt(encoded, 36),
// Custom extraction for head-based salt
extractSalt(envelope, saltLen) {
return envelope.slice(0, saltLen);
},
stripSalt(envelope, saltLen) {
return envelope.slice(saltLen);
}
};Here are examples of how different developers create their unique rules:
import { defaultRules } from "fise";
const rules = {
...defaultRules,
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
return (c.length * 7 + (t % 11)) % c.length;
}
};import { defaultRules } from "fise";
const rules = {
...defaultRules,
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
return (c.length * 13 + (t % 17)) % c.length; // Different primes!
}
};const rules = {
offset(c) {
return Math.floor(c.length / 2);
},
encodeLength: len => len.toString(36).padStart(2, "0"),
decodeLength: encoded => parseInt(encoded, 36)
};const rules = {
offset(c) {
return c.length % 7;
},
encodeLength: len => len.toString(36).padStart(2, "0"),
decodeLength: encoded => parseInt(encoded, 36)
};const rules = {
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
const len = c.length || 1;
const saltLen = ctx.saltLength ?? 10;
return (len * 17 + (t % 23) + (saltLen * 3)) % len;
},
encodeLength: len => len.toString(36).padStart(2, "0"),
decodeLength: encoded => parseInt(encoded, 36)
};const rules = {
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
return (c.length ^ t) % c.length;
},
encodeLength: len => len.toString(36).padStart(2, "0"),
decodeLength: encoded => parseInt(encoded, 36)
};const rules = {
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
// Using prime numbers for better distribution
return (c.length * 3 + (t % 7)) % c.length;
},
encodeLength: len => len.toString(36).padStart(2, "0"),
decodeLength: encoded => parseInt(encoded, 36)
};const rules = {
offset(c, ctx) {
const t = ctx.timestamp ?? 0;
return (c.length * 7 + (t % 11)) % c.length;
},
encodeLength(len) {
const base62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let result = "";
let n = len;
do {
result = base62[n % 62] + result;
n = Math.floor(n / 62);
} while (n > 0);
return result.padStart(2, "0");
},
decodeLength(encoded) {
const base62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
let result = 0;
for (let i = 0; i < encoded.length; i++) {
result = result * 62 + base62.indexOf(encoded[i]);
}
return result;
}
};Each developer's unique approach creates:
- ✅ Unique encryption dialect per app
- ✅ No universal decoder across deployments
- ✅ Natural diversity without coordination
- ✅ Easy rotation - just change the offset function
If Everyone Used the Same Rules:
// Everyone uses this
const sameRules = {
offset: (c, ctx) => (c.length * 7 + (ctx.timestamp % 11)) % c.length,
encodeLength: len => len.toString(36).padStart(2, "0"),
decodeLength: encoded => parseInt(encoded, 36)
};Result: ❌ Universal decoder exists → weak security
When Everyone Writes Their Own:
// Developer A
offset: (c, ctx) => (c.length * 7 + (ctx.timestamp % 11)) % c.length
// Developer B
offset: (c, ctx) => (c.length * 13 + (ctx.timestamp % 17)) % c.length
// Developer C
offset: (c) => Math.floor(c.length / 2)
// Developer D
offset: (c, ctx) => (c.length ^ ctx.timestamp) % c.lengthResult: ✅ No universal decoder → strong security through diversity
-
Always customize
offset()- This is the primary security point- Use different multipliers/modulos per app
- Consider using app-specific constants
- Just copy
defaultRulesand change the multiplier/modulo - that's it!
-
Use timestamp for rotation - Pass
timestampin options (backend only):// backend.ts fiseEncrypt(text, rules, { timestamp: Math.floor(Date.now() / 60000) });
-
Rotate rules periodically - Change your offset function over time
- Makes decoders stale quickly
- Increases attacker maintenance cost
-
Keep it simple - The 3 security points are enough for most use cases
- Simple is better than complex
- Easy to understand = easy to maintain
- Easy to rotate = better security
-
Test your rules - Ensure they work correctly:
// Test your rules const encrypted = fiseEncrypt("test", myRules); const decrypted = fiseDecrypt(encrypted, myRules); console.assert(decrypted === "test", "Rules work!");
Traditional approach:
- Complex crypto → only experts can use → limited diversity → weak security
FISE approach:
- Simple rules → any developer can use → natural diversity → strong security
- View FISE Examples Repository — see real-world examples, demos, and production-ready code
- Read WHITEPAPER.md for deeper understanding
- Check SECURITY.md for security considerations
- Explore USE_CASES.md for real-world scenarios
- See RULES.md for advanced rule customization
Q: Do I need to implement all 3 methods?
A: Yes, all 3 are required. But they're simple - see the examples above.
Q: Can I use the same rules for multiple apps?
A: No! Each app should have a unique offset() function. This is the core security principle.
Q: What if I want more control?
A: You can optionally override saltRange, extractSalt, and stripSalt, but the 3 security points are sufficient.
Q: Is the builder necessary?
A: No! The builder is optional. Most users just copy defaultRules and modify the offset, or define the 3 methods directly.
Q: What's the easiest way to create rules?
A: Copy defaultRules and modify the offset() function - that's it! Just change the multiplier/modulo to make it unique. See the examples above - most developers just copy and modify!
Q: Do I need to be a cryptography expert?
A: No! FISE empowers every developer to create their own unique encryption dialect. You don't need cryptography expertise - you just need to write your unique offset function. The simplicity is the power.