Skip to content

Instantly share code, notes, and snippets.

@JTRNS
Created September 3, 2022 08:43
Show Gist options
  • Save JTRNS/f61407bf52ca18c929180d528aea51c4 to your computer and use it in GitHub Desktop.
Save JTRNS/f61407bf52ca18c929180d528aea51c4 to your computer and use it in GitHub Desktop.
Generate and export RSA-OAEP encryption keys from the browser.
function encodeMessage(message: string) {
const enc = new TextEncoder();
return enc.encode(message);
}
function decodeMessage(buffer: ArrayBuffer) {
const dec = new TextDecoder();
return dec.decode(buffer);
}
function ab2str(buffer: ArrayBuffer): string {
return String.fromCharCode.apply(null, new Uint8Array(buffer));
}
function str2ab(str: string) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
function hexEncode(str: string) {
return str
.split('')
.map((c) => c.charCodeAt(0).toString(16).padStart(2, '0'))
.join('');
}
function hexDecode(hex: string) {
return hex
.split(/(\w\w)/g)
.filter((p) => !!p)
.map((c) => String.fromCharCode(parseInt(c, 16)))
.join('');
}
type EncryptedMessage = {
buffer: ArrayBuffer;
hex: string;
};
export async function encryptMessage(
message: string,
publicKey: CryptoKey | string
): Promise<EncryptedMessage> {
if (typeof publicKey === 'string')
publicKey = await importPublicKey(publicKey);
const encoded = encodeMessage(message);
const encryptedBuffer: ArrayBuffer = await crypto.subtle.encrypt(
{
name: 'RSA-OAEP',
},
publicKey,
encoded
);
return {
buffer: encryptedBuffer,
hex: hexEncode(ab2str(encryptedBuffer)),
};
}
export async function decryptMessage(
encryptedMessage: EncryptedMessage['hex'] | EncryptedMessage,
privateKey: CryptoKey | string
) {
const encryptedBuffer =
typeof encryptedMessage === 'string'
? str2ab(hexDecode(encryptedMessage))
: encryptedMessage.buffer;
if (typeof privateKey === 'string') {
privateKey = await importPrivateKey(privateKey);
}
const decrypted: ArrayBuffer = await crypto.subtle.decrypt(
{
name: 'RSA-OAEP',
},
privateKey,
encryptedBuffer
);
return decodeMessage(decrypted);
}
function stripPublicKeyHeaderFooter(publicKey: string) {
const H = '-----BEGIN PUBLIC KEY-----';
const F = '-----END PUBLIC KEY-----';
return publicKey.substring(H.length, publicKey.length - F.length);
}
function stripPrivateKeyHeaderFooter(privateKey: string) {
const H = '-----BEGIN PRIVATE KEY-----';
const F = '-----END PRIVATE KEY-----';
return privateKey.substring(H.length, privateKey.length - F.length);
}
export function importPublicKey(publicKey: string) {
const keyContent = stripPublicKeyHeaderFooter(publicKey);
const binaryDerString = atob(keyContent);
const binaryDer = str2ab(binaryDerString);
return crypto.subtle.importKey(
'spki',
binaryDer,
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['encrypt']
);
}
export function importPrivateKey(privateKey: string) {
const keyContent = stripPrivateKeyHeaderFooter(privateKey);
const binaryDerString = atob(keyContent);
const binaryDer = str2ab(binaryDerString);
return crypto.subtle.importKey(
'pkcs8',
binaryDer,
{
name: 'RSA-OAEP',
hash: 'SHA-256',
},
true,
['decrypt']
);
}
async function exportPrivateKey(key: CryptoKey): Promise<PrivateKey> {
const exported = await crypto.subtle.exportKey('pkcs8', key);
const exportedAsString = ab2str(exported);
const exportedAsBase64 = btoa(exportedAsString);
return `-----BEGIN PRIVATE KEY-----\n${exportedAsBase64}\n-----END PRIVATE KEY-----`;
}
async function exportPublicKey(key: CryptoKey): Promise<PublicKey> {
const exported = await crypto.subtle.exportKey('spki', key);
const exportedAsString = ab2str(exported);
const exportedAsBase64 = btoa(exportedAsString);
return `-----BEGIN PUBLIC KEY-----\n${exportedAsBase64}\n-----END PUBLIC KEY-----`;
}
type PublicKey = string;
type PrivateKey = string;
export async function generateKeyPair(): Promise<[PublicKey, PrivateKey]> {
const keyPair = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 4096,
publicExponent: new Uint8Array([1, 0, 1]),
hash: 'SHA-256',
},
true,
['encrypt', 'decrypt']
);
return Promise.all([
exportPublicKey(keyPair.publicKey),
exportPrivateKey(keyPair.privateKey),
]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment