-
-
Save paschalidi/2faaba843bf201cbffce319b1c542d9c to your computer and use it in GitHub Desktop.
Web Crypto API example: RSA keygen & export & import & sign & verify & encrypt & decrypt
This file contains 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
<!-- MIT License --> | |
<html> | |
<head> | |
<script> | |
function generateKey(alg, scope) { | |
return new Promise(function(resolve) { | |
var genkey = crypto.subtle.generateKey(alg, true, scope) | |
genkey.then(function (pair) { | |
resolve(pair) | |
}) | |
}) | |
} | |
function arrayBufferToBase64String(arrayBuffer) { | |
var byteArray = new Uint8Array(arrayBuffer) | |
var byteString = '' | |
for (var i=0; i<byteArray.byteLength; i++) { | |
byteString += String.fromCharCode(byteArray[i]) | |
} | |
return btoa(byteString) | |
} | |
function base64StringToArrayBuffer(b64str) { | |
var byteStr = atob(b64str) | |
var bytes = new Uint8Array(byteStr.length) | |
for (var i = 0; i < byteStr.length; i++) { | |
bytes[i] = byteStr.charCodeAt(i) | |
} | |
return bytes.buffer | |
} | |
function textToArrayBuffer(str) { | |
var buf = unescape(encodeURIComponent(str)) // 2 bytes for each char | |
var bufView = new Uint8Array(buf.length) | |
for (var i=0; i < buf.length; i++) { | |
bufView[i] = buf.charCodeAt(i) | |
} | |
return bufView | |
} | |
function arrayBufferToText(arrayBuffer) { | |
var byteArray = new Uint8Array(arrayBuffer) | |
var str = '' | |
for (var i=0; i<byteArray.byteLength; i++) { | |
str += String.fromCharCode(byteArray[i]) | |
} | |
return str | |
} | |
function arrayBufferToBase64(arr) { | |
return btoa(String.fromCharCode.apply(null, new Uint8Array(arr))) | |
} | |
function convertBinaryToPem(binaryData, label) { | |
var base64Cert = arrayBufferToBase64String(binaryData) | |
var pemCert = "-----BEGIN " + label + "-----\r\n" | |
var nextIndex = 0 | |
var lineLength | |
while (nextIndex < base64Cert.length) { | |
if (nextIndex + 64 <= base64Cert.length) { | |
pemCert += base64Cert.substr(nextIndex, 64) + "\r\n" | |
} else { | |
pemCert += base64Cert.substr(nextIndex) + "\r\n" | |
} | |
nextIndex += 64 | |
} | |
pemCert += "-----END " + label + "-----\r\n" | |
return pemCert | |
} | |
function convertPemToBinary(pem) { | |
var lines = pem.split('\n') | |
var encoded = '' | |
for(var i = 0;i < lines.length;i++){ | |
if (lines[i].trim().length > 0 && | |
lines[i].indexOf('-BEGIN RSA PRIVATE KEY-') < 0 && | |
lines[i].indexOf('-BEGIN RSA PUBLIC KEY-') < 0 && | |
lines[i].indexOf('-END RSA PRIVATE KEY-') < 0 && | |
lines[i].indexOf('-END RSA PUBLIC KEY-') < 0) { | |
encoded += lines[i].trim() | |
} | |
} | |
return base64StringToArrayBuffer(encoded) | |
} | |
function importPublicKey(pemKey) { | |
return new Promise(function(resolve) { | |
var importer = crypto.subtle.importKey("spki", convertPemToBinary(pemKey), signAlgorithm, true, ["verify"]) | |
importer.then(function(key) { | |
resolve(key) | |
}) | |
}) | |
} | |
function importPrivateKey(pemKey) { | |
return new Promise(function(resolve) { | |
var importer = crypto.subtle.importKey("pkcs8", convertPemToBinary(pemKey), signAlgorithm, true, ["sign"]) | |
importer.then(function(key) { | |
resolve(key) | |
}) | |
}) | |
} | |
function exportPublicKey(keys) { | |
return new Promise(function(resolve) { | |
window.crypto.subtle.exportKey('spki', keys.publicKey). | |
then(function(spki) { | |
resolve(convertBinaryToPem(spki, "RSA PUBLIC KEY")) | |
}) | |
}) | |
} | |
function exportPrivateKey(keys) { | |
return new Promise(function(resolve) { | |
var expK = window.crypto.subtle.exportKey('pkcs8', keys.privateKey) | |
expK.then(function(pkcs8) { | |
resolve(convertBinaryToPem(pkcs8, "RSA PRIVATE KEY")) | |
}) | |
}) | |
} | |
function exportPemKeys(keys) { | |
return new Promise(function(resolve) { | |
exportPublicKey(keys).then(function(pubKey) { | |
exportPrivateKey(keys).then(function(privKey) { | |
resolve({publicKey: pubKey, privateKey: privKey}) | |
}) | |
}) | |
}) | |
} | |
function signData(key, data) { | |
return window.crypto.subtle.sign(signAlgorithm, key, textToArrayBuffer(data)) | |
} | |
function testVerifySig(pub, sig, data) { | |
return crypto.subtle.verify(signAlgorithm, pub, sig, data) | |
} | |
function encryptData(vector, key, data) { | |
return crypto.subtle.encrypt( | |
{ | |
name: "RSA-OAEP", | |
iv: vector | |
}, | |
key, | |
textToArrayBuffer(data) | |
) | |
} | |
function decryptData(vector, key, data) { | |
return crypto.subtle.decrypt( | |
{ | |
name: "RSA-OAEP", | |
iv: vector | |
}, | |
key, | |
data | |
) | |
} | |
// Test everything | |
var signAlgorithm = { | |
name: "RSASSA-PKCS1-v1_5", | |
hash: { | |
name: "SHA-256" | |
}, | |
modulusLength: 2048, | |
extractable: false, | |
publicExponent: new Uint8Array([1, 0, 1]) | |
} | |
var encryptAlgorithm = { | |
name: "RSA-OAEP", | |
modulusLength: 2048, | |
publicExponent: new Uint8Array([1, 0, 1]), | |
extractable: false, | |
hash: { | |
name: "SHA-256" | |
} | |
} | |
var crypto = window.crypto || window.msCrypto | |
if (crypto.subtle) { | |
var _signedData | |
var _data = "test" | |
var scopeSign = ["sign", "verify"] | |
var scopeEncrypt = ["encrypt", "decrypt"] | |
var vector = crypto.getRandomValues(new Uint8Array(16)) | |
// Test signature | |
generateKey(signAlgorithm, scopeSign).then(function(pair) { | |
exportPemKeys(pair).then(function(keys) { | |
var title = document.createElement('h2') | |
title.innerHTML = 'Signature' | |
document.querySelector('body').appendChild(title) | |
var divS = document.createElement('div') | |
var divP = document.createElement('div') | |
divS.innerHTML = JSON.stringify(keys.privateKey) | |
divP.innerHTML = JSON.stringify(keys.publicKey) | |
document.querySelector('body').appendChild(divS) | |
document.querySelector('body').appendChild(document.createElement('br')) | |
document.querySelector('body').appendChild(divP) | |
signData(pair.privateKey, _data).then(function(signedData) { | |
var sigT = document.createElement('h2') | |
sigT.innerHTML = 'Signature:' | |
document.querySelector('body').appendChild(sigT) | |
var divSig = document.createElement('div') | |
divSig.innerHTML = arrayBufferToBase64(signedData) | |
document.querySelector('body').appendChild(divSig) | |
_signedData = signedData | |
testVerifySig(pair.publicKey, signedData, textToArrayBuffer(_data)).then(function(result) { | |
var verT = document.createElement('h2') | |
verT.innerHTML = 'Signature outcome:' | |
document.querySelector('body').appendChild(verT) | |
var divOut = document.createElement('div') | |
divOut.innerHTML = (result)?'Success':'Failed'; | |
document.querySelector('body').appendChild(divOut) | |
}) | |
}) | |
// load keys and re-check signature | |
importPublicKey(keys.publicKey).then(function(key) { | |
testVerifySig(key, _signedData, textToArrayBuffer(_data)).then(function(result) { | |
console.log("Signature verified after importing PEM public key:", result) | |
}) | |
}) | |
// should output `Signature verified: true` twice in the console | |
}) | |
}) | |
// Test encryption | |
generateKey(encryptAlgorithm, scopeEncrypt).then(function(keys) { | |
var title = document.createElement('h2') | |
title.innerHTML = 'Encryption' | |
document.querySelector('body').appendChild(title) | |
encryptData(vector, keys.publicKey, _data).then(function(encryptedData) { | |
var sigT = document.createElement('h2') | |
sigT.innerHTML = 'Encrypted text:' | |
document.querySelector('body').appendChild(sigT) | |
var divSig = document.createElement('div') | |
divSig.innerHTML = arrayBufferToBase64(encryptedData) | |
document.querySelector('body').appendChild(divSig) | |
decryptData(vector, keys.privateKey, encryptedData).then(function(result) { | |
var verT = document.createElement('h2') | |
verT.innerHTML = 'Encryption outcome:' | |
document.querySelector('body').appendChild(verT) | |
var divOut = document.createElement('div') | |
divOut.innerHTML = (arrayBufferToText(result) === _data)?'Success':'Failed'; | |
document.querySelector('body').appendChild(divOut) | |
}) | |
}) | |
}) | |
} | |
</script> | |
</head> | |
<body></body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment