Skip to content

Instantly share code, notes, and snippets.

@ronanyeah
Created July 5, 2024 17:23
Show Gist options
  • Save ronanyeah/edb1b319aba8e6618cbe584c31257104 to your computer and use it in GitHub Desktop.
Save ronanyeah/edb1b319aba8e6618cbe584c31257104 to your computer and use it in GitHub Desktop.
Linux OpenSSL + Browser Web Crypto interop
const opensslPrefix = new TextEncoder().encode("Salted__");
async function encryptBuffer(data: Uint8Array, password: string) {
const salt = crypto.getRandomValues(new Uint8Array(8));
const { iv, key } = await passwordToKeyAndIV(password, salt);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv: iv },
key,
data
);
const encryptedArray = new Uint8Array(encrypted);
const buffer = new Uint8Array(
opensslPrefix.length + salt.length + encryptedArray.length
);
buffer.set(opensslPrefix, 0);
buffer.set(salt, opensslPrefix.length);
buffer.set(encryptedArray, opensslPrefix.length + salt.length);
return encodeBase64(buffer);
}
async function decryptBuffer(workingBuffer: Uint8Array, password: string) {
const encryptedBuffer = workingBuffer.slice(opensslPrefix.length);
const salt = encryptedBuffer.slice(0, 8);
const encrypted = encryptedBuffer.slice(8);
const { key, iv } = await passwordToKeyAndIV(password, salt);
const decrypted = await crypto.subtle.decrypt(
{ name: "AES-CBC", iv: iv },
key,
encrypted
);
return new TextDecoder().decode(decrypted);
}
async function passwordToKeyAndIV(password: string, salt: Uint8Array) {
const baseKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(password),
{ name: "PBKDF2" },
false,
["deriveBits"]
);
// AES-256-CBC which requires a 256-bit key (32 bytes) + 128-bit IV (16 bytes)
const keyLength = 32 * 8; // 32 bytes for AES-256 key
const ivLength = 16 * 8; // 16 bytes for AES IV
const totalLength = keyLength + ivLength; // Total bits needed
const derivedBits = await crypto.subtle.deriveBits(
{
name: "PBKDF2",
salt: salt,
iterations: 100_000,
hash: "SHA-256",
},
baseKey,
totalLength
);
const keyBuffer = derivedBits.slice(0, 32); // First 32 bytes for the key
const ivBuffer = derivedBits.slice(32, 48); // Next 16 bytes for the IV
const key = await crypto.subtle.importKey(
"raw",
keyBuffer,
{ name: "AES-CBC", length: 256 },
false,
["encrypt", "decrypt"]
);
return { key, iv: new Uint8Array(ivBuffer) };
}
async function encryptText(plaintext: string, password: string) {
const encodedText = new TextEncoder().encode(plaintext);
return encryptBuffer(encodedText, password);
}
async function decryptText(encrypted: string, password: string) {
return decryptBuffer(decodeBase64(encrypted), password);
}
function encodeBase64(bytes: Uint8Array): string {
return btoa(String.fromCharCode.apply(null, Array.from(bytes)));
}
function decodeBase64(base64String: string): Uint8Array {
return Uint8Array.from(atob(base64String), (c) => c.charCodeAt(0));
}
echo "plaintext here" |\
openssl enc -aes-256-cbc -a -iter 100000 -md sha256 -p -salt -pbkdf2 -pass pass:hunter2
echo "U2FsdGVkX19vQGF13Gv2Ce9VsjegORo8guUGKbMmY5A=" |\
openssl enc -aes-256-cbc -iter 100000 -md sha256 -a -d -pbkdf2 -pass pass:hunter2
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment