Web Crypto API
Encrypted output:
Decrypted output:
Markup
<label>
Message to be encrypted
<input type="text" name="encryption-input" id="encryption-input" />
</label>
<button id="encrypt">Encrypt</button>
<p>Encrypted output: <span id="encryption-output"></span></p>
<div class="hidden" id="decryption">
<button id="decrypt">Decrypt</button>
<p>Decrypted output: <span id="decryption-output"></span></p>
</div>
<span class="error"></span>
Styles
label {
margin-top: var(--space-lg);
display: block;
}
#encryption-output {
font-size: var(--font-size-sm);
font-style: italic;
}
.error:empty {
display: none;
}
.error {
color: red;
display: block;
margin-top: var(--space-sm);
}
Script
const encryptButton = document.getElementById('encrypt')
const decryptButton = document.getElementById('decrypt')
const decryptionBlock = document.getElementById('decryption')
const encryptionInput = document.getElementById('encryption-input')
const encryptionOutput = document.getElementById('encryption-output')
const decryptionOutput = document.getElementById('decryption-output')
const errorElement = document.querySelector('.error')
let decryptionKey = null
encryptButton.addEventListener('click', async () => {
reset()
try {
const { encrypted, privateKey } = await encrypt()
decryptionKey = privateKey
encryptionOutput.textContent = arrayBufferToHex(encrypted)
encryptionInput.value = ''
decryptionBlock.classList.remove('hidden')
} catch (error) {
errorElement.textContent = error.message
encryptionOutput.textContent = ''
}
})
decryptButton.addEventListener('click', async () => {
reset()
try {
const { decrypted } = await decrypt()
decryptionOutput.textContent = decrypted
} catch (error) {
errorElement.textContent = error.message
decryptionOutput.textContent = ''
}
})
function getEncodedString(string) {
let encoder = new TextEncoder()
return encoder.encode(string)
}
function getDecodedString(buffer) {
let decoder = new TextDecoder()
return decoder.decode(buffer)
}
function arrayBufferToHex(buffer) {
const byteArray = new Uint8Array(buffer)
return Array.from(byteArray)
.map((byte) => byte.toString(16).padStart(2, '0'))
.join('')
}
function hexToArrayBuffer(hex) {
const byteArray = new Uint8Array(hex.length / 2)
for (let i = 0; i < byteArray.length; i++) {
byteArray[i] = parseInt(hex.substr(i * 2, 2), 16)
}
return byteArray.buffer
}
async function decrypt() {
const data = hexToArrayBuffer(encryptionOutput.textContent)
const decryptedString = await crypto.subtle.decrypt('RSA-OAEP', decryptionKey, data)
const decodedString = getDecodedString(decryptedString)
return {
decrypted: decodedString,
}
}
async function encrypt() {
if (!encryptionInput.value) {
throw new Error('Please enter a message')
}
const encodedMessage = getEncodedString(encryptionInput.value)
const { publicKey, privateKey } = await crypto.subtle.generateKey(
{
name: 'RSA-OAEP',
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: { name: 'SHA-256' },
},
true,
['encrypt', 'decrypt']
)
const encrypted = await crypto.subtle.encrypt('RSA-OAEP', publicKey, encodedMessage)
return {
publicKey,
privateKey,
encrypted,
}
}
function reset() {
errorElement.textContent = ''
decryptionOutput.textContent = ''
}