Last active
August 15, 2023 16:44
-
-
Save davidsonsns/795a9d718eb718b67ba96e6686417232 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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