Created
September 3, 2022 08:43
-
-
Save JTRNS/f61407bf52ca18c929180d528aea51c4 to your computer and use it in GitHub Desktop.
Generate and export RSA-OAEP encryption keys from the browser.
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
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