Skip to content

Instantly share code, notes, and snippets.

@davidsonsns
Last active August 15, 2023 16:44
Show Gist options
  • Save davidsonsns/795a9d718eb718b67ba96e6686417232 to your computer and use it in GitHub Desktop.
Save davidsonsns/795a9d718eb718b67ba96e6686417232 to your computer and use it in GitHub Desktop.
const cloakedStringRegex =
/^v1\.aesgcm256\.(?<fingerprint>[0-9a-fA-F]{8})\.(?<iv>[a-zA-Z0-9-_]{16})\.(?<ciphertext>[a-zA-Z0-9-_]{22,})={0,2}$/;
const cloakKeyRegex = /^k1\.aesgcm256\.(?<key>[a-zA-Z0-9-_]{43}=?)$/;
const INVALID_BYTE = 256;
class Coder {
constructor(_paddingCharacter = '=') {
this._paddingCharacter = _paddingCharacter;
}
encodedLength(length) {
if (!this._paddingCharacter) {
return ((length * 8 + 5) / 6) | 0;
}
return (((length + 2) / 3) * 4) | 0;
}
encode(data) {
let out = '';
let i = 0;
for (; i < data.length - 2; i += 3) {
const c = (data[i] << 16) | (data[i + 1] << 8) | data[i + 2];
out += this._encodeByte((c >>> (3 * 6)) & 63);
out += this._encodeByte((c >>> (2 * 6)) & 63);
out += this._encodeByte((c >>> (1 * 6)) & 63);
out += this._encodeByte((c >>> (0 * 6)) & 63);
}
const left = data.length - i;
if (left > 0) {
const c = (data[i] << 16) | (left === 2 ? data[i + 1] << 8 : 0);
out += this._encodeByte((c >>> (3 * 6)) & 63);
out += this._encodeByte((c >>> (2 * 6)) & 63);
if (left === 2) {
out += this._encodeByte((c >>> (1 * 6)) & 63);
} else {
out += this._paddingCharacter || '';
}
out += this._paddingCharacter || '';
}
return out;
}
maxDecodedLength(length) {
if (!this._paddingCharacter) {
return ((length * 6 + 7) / 8) | 0;
}
return ((length / 4) * 3) | 0;
}
decodedLength(s) {
return this.maxDecodedLength(s.length - this._getPaddingLength(s));
}
decode(s) {
if (s.length === 0) {
return new Uint8Array(0);
}
const paddingLength = this._getPaddingLength(s);
const length = s.length - paddingLength;
const out = new Uint8Array(this.maxDecodedLength(length));
let op = 0;
let i = 0;
let haveBad = 0;
let v0 = 0,
v1 = 0,
v2 = 0,
v3 = 0;
for (; i < length - 4; i += 4) {
v0 = this._decodeChar(s.charCodeAt(i + 0));
v1 = this._decodeChar(s.charCodeAt(i + 1));
v2 = this._decodeChar(s.charCodeAt(i + 2));
v3 = this._decodeChar(s.charCodeAt(i + 3));
out[op++] = (v0 << 2) | (v1 >>> 4);
out[op++] = (v1 << 4) | (v2 >>> 2);
out[op++] = (v2 << 6) | v3;
haveBad |= v0 & INVALID_BYTE;
haveBad |= v1 & INVALID_BYTE;
haveBad |= v2 & INVALID_BYTE;
haveBad |= v3 & INVALID_BYTE;
}
if (i < length - 1) {
v0 = this._decodeChar(s.charCodeAt(i));
v1 = this._decodeChar(s.charCodeAt(i + 1));
out[op++] = (v0 << 2) | (v1 >>> 4);
haveBad |= v0 & INVALID_BYTE;
haveBad |= v1 & INVALID_BYTE;
}
if (i < length - 2) {
v2 = this._decodeChar(s.charCodeAt(i + 2));
out[op++] = (v1 << 4) | (v2 >>> 2);
haveBad |= v2 & INVALID_BYTE;
}
if (i < length - 3) {
v3 = this._decodeChar(s.charCodeAt(i + 3));
out[op++] = (v2 << 6) | v3;
haveBad |= v3 & INVALID_BYTE;
}
if (haveBad !== 0) {
throw new Error('Base64Coder: incorrect characters for decoding');
}
return out;
}
_encodeByte(b) {
let result = b;
result += 65;
result += ((25 - b) >>> 8) & (0 - 65 - 26 + 97);
result += ((51 - b) >>> 8) & (26 - 97 - 52 + 48);
result += ((61 - b) >>> 8) & (52 - 48 - 62 + 43);
result += ((62 - b) >>> 8) & (62 - 43 - 63 + 47);
return String.fromCharCode(result);
}
_decodeChar(c) {
let result = INVALID_BYTE;
result += (((42 - c) & (c - 44)) >>> 8) & (-INVALID_BYTE + c - 43 + 62);
result += (((46 - c) & (c - 48)) >>> 8) & (-INVALID_BYTE + c - 47 + 63);
result += (((47 - c) & (c - 58)) >>> 8) & (-INVALID_BYTE + c - 48 + 52);
result += (((64 - c) & (c - 91)) >>> 8) & (-INVALID_BYTE + c - 65 + 0);
result += (((96 - c) & (c - 123)) >>> 8) & (-INVALID_BYTE + c - 97 + 26);
return result;
}
_getPaddingLength(s) {
let paddingLength = 0;
if (this._paddingCharacter) {
for (let i = s.length - 1; i >= 0; i--) {
if (s[i] !== this._paddingCharacter) {
break;
}
paddingLength++;
}
if (s.length < 4 || paddingLength > 2) {
throw new Error('Base64Coder: incorrect padding');
}
}
return paddingLength;
}
}
class URLSafeCoder extends Coder {
_encodeByte(b) {
let result = b;
result += 65;
result += ((25 - b) >>> 8) & (0 - 65 - 26 + 97);
result += ((51 - b) >>> 8) & (26 - 97 - 52 + 48);
result += ((61 - b) >>> 8) & (52 - 48 - 62 + 45);
result += ((62 - b) >>> 8) & (62 - 45 - 63 + 95);
return String.fromCharCode(result);
}
_decodeChar(c) {
let result = INVALID_BYTE;
result += (((44 - c) & (c - 46)) >>> 8) & (-INVALID_BYTE + c - 45 + 62);
result += (((94 - c) & (c - 96)) >>> 8) & (-INVALID_BYTE + c - 95 + 63);
result += (((47 - c) & (c - 58)) >>> 8) & (-INVALID_BYTE + c - 48 + 52);
result += (((64 - c) & (c - 91)) >>> 8) & (-INVALID_BYTE + c - 65 + 0);
result += (((96 - c) & (c - 123)) >>> 8) & (-INVALID_BYTE + c - 97 + 26);
return result;
}
}
const urlSafeCoder = new URLSafeCoder();
const b64 = {
urlSafe: (str) => str.replace(/\+/g, '-').replace(/\//g, '_'),
decode: function b64Decode(input) {
if (typeof Buffer !== 'undefined') {
const buf = Buffer.from(input, 'base64');
return new Uint8Array(buf, 0, buf.length);
}
return urlSafeCoder.decode(b64.urlSafe(input));
},
};
const utf8Encoder = new TextEncoder();
const utf8Decoder = new TextDecoder();
const utf8 = {
decode: function Utf8Decode(input) {
return utf8Decoder.decode(input);
},
encode: function Utf8Encode(input) {
const buf = utf8Encoder.encode(input);
return new Uint8Array(buf, 0, buf.length);
},
};
function decryptAesGcmSync(key, cipher) {
const decipher = CryptoJS.createDecipheriv('aes-256-gcm', key, cipher.iv);
const tagStart = cipher.text.length - 16;
const msg = cipher.text.slice(0, tagStart);
const tag = cipher.text.slice(tagStart);
decipher.setAuthTag(tag);
return decipher.update(msg, undefined, 'utf8') + decipher.final('utf8');
}
function importKeySync(key) {
const match = key.match(cloakKeyRegex);
if (!match) {
throw new Error('Unknown key format');
}
return b64.decode(match.groups.key);
}
async function decryptString(input, key) {
const match = input.match(cloakedStringRegex);
if (!match) {
throw new Error(`Unknown message format: ${input}`);
}
const iv = match.groups.iv;
const ciphertext = match.groups.ciphertext;
const cipher = { iv: b64.decode(iv), text: b64.decode(ciphertext) };
const aesKey = importKeySync(key);
if (typeof window === 'undefined') {
return decryptAesGcmSync(aesKey, cipher);
} else {
// Browser - use WebCrypto
const buf = await crypto.subtle.decrypt({ name: 'AES-GCM', iv: cipher.iv }, key, cipher.text);
return utf8.decode(new Uint8Array(buf));
}
}
window.decryptString = decryptString;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment