This is the 1st post in the Publicly Hide Magic Strings series (»). A common form of website Easter eggs is magic strings, but how can they be hidden in a public repository? Encryption. In this post, I’ll develop a magic string encryption tool based on crypto-js.
Input Q
and A
below.
Here will show the keys KQ
and KA
, which can be saved in the public repository.
Finally input your Q
or any of the following to test. Only the encrypted pairs of (KQ, KA)
are used in code.
Y7K4
Meow?
We will, we will
Introduction
I have two expectations for my GitHub Pages.
- As a platform sharing my ideas, especially in technical fields, it should be hosted on a public repository so that everyone can access the source code.
- It should have some hidden Easter eggs. Just for fun.
There are various ways to hide Easter eggs on a website: code comments, Konami code, hidden buttons, magic strings, etc. These methods are all great, and I especially prefer magic strings as it can be naturally embedded in the search box.
However, nothing is really a secret in a public repository. If the website visitor reads the source code, the secret is not hidden anymore, unless it’s carefully encrypted.
Design
Concepts
Let Q
be a magic string and A
be the corresponding response, i.e., when the user inputs Q
in the search box, the website should display A
in addition to the search results. Multiple pairs of (Q, A)
form the website Easter eggs.
After encryption, multiple pairs of keys (KQ, KA)
are explicitly stored in the public repository. It should be practically impossible to know (Q, A)
based on (KQ, KA)
.
Workflow
Given a pair (Q, A)
, compute the key pair (KQ, KA)
as follows and save it in the public repository. Note that Q
(after hashing) is used as the key for encryption. This avoids people from getting A
without even knowing Q
.
When the user input Q
is received, compute its hash KQ
and look up in the list of keys. If KQ
is found, decrypt the corresponding KA
with Q
as the key and get the result A
; otherwise do nothing.
Algorithms
Hash
The hash function I use is PBKDF2, which is a common choice for password hashing. Note that if the number of iterations is too large, then the PBKDF2 becomes slow, making the search box less responsive; but if it’s too small, then the hash becomes less secure.
// KQ = hash(Q)
function hash(Q) {
const salt = "Y7K4's magic salt for PBKDF2";
var KQ = CryptoJS.enc.Base64.stringify(
CryptoJS.PBKDF2(Q, salt, { iterations: 1000 })
);
return KQ;
}
Encrypt & Decrypt
The cipher I use is Rabbit, which is a high-speed stream cipher.
// KA = encrypt(A, Q)
function encrypt(A, Q) {
var key = CryptoJS.SHA256(Q);
var KA = CryptoJS.Rabbit.encrypt(A, key);
return KA;
}
// A = decrypt(KA, Q)
function decrypt(KA, Q) {
var key = CryptoJS.SHA256(Q);
var A = CryptoJS.enc.Utf8.stringify(CryptoJS.Rabbit.decrypt(KA, key));
return A;
}
What’s next
- Embed the magic string system into the search box.
- Create a lot of magic strings for fun.