Skip to content

Instantly share code, notes, and snippets.

@aggregate1166877
Forked from deiu/webcryptoapi.html
Created November 17, 2023 16:35
Show Gist options
  • Save aggregate1166877/cb0e4f2c29f63ed989c4fccc0b097578 to your computer and use it in GitHub Desktop.
Save aggregate1166877/cb0e4f2c29f63ed989c4fccc0b097578 to your computer and use it in GitHub Desktop.
Web Crypto API example: RSA keygen & export & import & sign & verify & encrypt & decrypt
<!-- 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