In diesem Tutorial werfen wir einen Blick auf die Web Cryptography API : eine clientseitige Datenverschlüsselungsschnittstelle. Dieses Tutorial basiert auf diesem Artikel . Es wird davon ausgegangen, dass Sie mit der Verschlüsselung vertraut sind.
Was genau machen wir jetzt? Wir werden einen einfachen Server schreiben, der verschlüsselte Daten vom Client akzeptiert und auf Anfrage zurückgibt. Die Daten selbst werden clientseitig verarbeitet.
Der Server wird in Node.js mit Express implementiert, dem Client in JavaScript. Bootstrap wird für das Styling verwendet.
Der Projektcode ist hier .
Wenn Sie interessiert sind, folgen Sie mir bitte.
Ausbildung
Erstellen Sie ein Verzeichnis
crypto-tut:
mkdir crypto-tut
Wir gehen darauf ein und initialisieren das Projekt:
cd crypto-tut
npm init -y
Installieren
express:
npm i express
Installieren
nodemon:
npm i -D nodemon
Bearbeitung
package.json:
"main": "server.js",
"scripts": {
"start": "nodemon"
},
Projektstruktur:
crypto-tut
--node_modules
--src
--client.js
--index.html
--style.css
--package-lock.json
--package.json
--server.js
Inhalt
index.html:
<head>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
<script src="client.js" defer></source>
</head>
<body>
<div class="container">
<h3>Web Cryptography API Tutorial</h3>
<input type="text" value="Hello, World!" class="form-control">
<div class="btn-box">
<button class="btn btn-primary btn-send">Send message</button>
<button class="btn btn-success btn-get" disabled>Get message</button>
</div>
<output></output>
</div>
</body>
Inhalt
style.css:
h3,
.btn-box {
margin: .5em;
text-align: center;
}
input,
output {
display: block;
margin: 1em auto;
text-align: center;
}
output span {
color: green;
}
Server
Beginnen wir mit der Erstellung eines Servers.
Wir öffnen
server.js.
Wir verbinden Express und erstellen Instanzen der Anwendung und des Routers:
const express = require('express')
const app = express()
const router = express.Router()
Wir verbinden Middleware (Zwischenschicht zwischen Anfrage und Antwort):
//
app.use(express.json({
type: ['application/json', 'text/plain']
}))
//
app.use(router)
//
app.use(express.static('src'))
Wir erstellen eine Variable zum Speichern von Daten:
let data
Wir verarbeiten den Empfang von Daten vom Kunden:
router.post('/secure-api', (req, res) => {
//
data = req.body
//
console.log(data)
//
res.end()
})
Wir verarbeiten das Senden von Daten an den Kunden:
router.get('/secure-api', (req, res) => {
// JSON,
//
res.json(data)
})
Wir starten den Server:
app.listen(3000, () => console.log('Server ready'))
Wir führen den Befehl aus
npm start. Das Terminal zeigt die Meldung "Server bereit" an. Öffnen http://localhost:3000:
Hier sind wir mit dem Server fertig. Gehen Sie zur Clientseite der Anwendung.
Klient
Hier beginnt der Spaß.
Öffnen Sie die Datei
client.js.
Für die Datenverschlüsselung wird ein symmetrischer AES-GCM-Algorithmus verwendet. Solche Algorithmen ermöglichen die Verwendung desselben Schlüssels für die Ver- und Entschlüsselung.
Erstellen Sie eine symmetrische Schlüsselgenerierungsfunktion:
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey
const generateKey = async () =>
window.crypto.subtle.generateKey({
name: 'AES-GCM',
length: 256,
}, true, ['encrypt', 'decrypt'])
Die Daten müssen vor der Verschlüsselung in einen Bytestream codiert werden. Dies ist mit der TextEncoder-Klasse einfach möglich:
// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
const encode = data => {
const encoder = new TextEncoder()
return encoder.encode(data)
}
Als nächstes benötigen wir einen Ausführungsvektor (Initialisierungsvektor, IV), bei dem es sich um eine zufällige oder pseudozufällige Folge von Zeichen handelt, die dem Verschlüsselungsschlüssel hinzugefügt werden, um seine Sicherheit zu erhöhen:
// https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues
const generateIv = () =>
// https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams
window.crypto.getRandomValues(new Uint8Array(12))
Nach dem Erstellen der Hilfsfunktionen können wir die Verschlüsselungsfunktion implementieren. Diese Funktion muss eine Chiffre und eine IV zurückgeben, damit die Chiffre anschließend dekodiert werden kann:
const encrypt = async (data, key) => {
const encoded = encode(data)
const iv = generateIv()
const cipher = await window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv
}, key, encoded)
return {
cipher,
iv
}
}
Nach dem Verschlüsseln der Daten mit SubtleCrypto sind sie Puffer für binäre Rohdaten. Dies ist nicht das beste Format für die Übertragung und Speicherung. Lassen Sie uns das beheben.
Die Daten werden normalerweise im JSON-Format gesendet und in einer Datenbank gespeichert. Daher ist es sinnvoll, die Daten in ein tragbares Format zu packen. Eine Möglichkeit, dies zu tun, besteht darin, die Daten in base64-Zeichenfolgen zu konvertieren:
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
const pack = buffer => window.btoa(
String.fromCharCode.apply(null, new Uint8Array(buffer))
)
Nach dem Empfang der Daten müssen Sie den umgekehrten Prozess ausführen, d. H. Konvertieren Sie Base64-codierte Zeichenfolgen in binäre Rohpuffer:
// https://developers.google.com/web/updates/2012/06/How-to-convert-ArrayBuffer-to-and-from-String
const unpack = packed => {
const string = window.atob(packed)
const buffer = new ArrayBuffer(string.length)
const bufferView = new Uint8Array(buffer)
for (let i = 0; i < string.length; i++) {
bufferView[i] = string.charCodeAt(i)
}
return buffer
}
Es bleibt, die empfangenen Daten zu entschlüsseln. Nach der Entschlüsselung müssen wir den Bytestream jedoch in sein ursprüngliches Format dekodieren. Dies kann mit der TextDecoder-Klasse erfolgen:
// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
const decode = byteStream => {
const decoder = new TextDecoder()
return decoder.decode(byteStream)
}
Die Entschlüsselungsfunktion ist die Umkehrung der Verschlüsselungsfunktion:
// https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/decrypt
const decrypt = async (cipher, key, iv) => {
const encoded = await window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv
}, key, cipher)
return decode(encoded)
}
Zu diesem Zeitpunkt
client.jssieht der Inhalt folgendermaßen aus:
const generateKey = async () =>
window.crypto.subtle.generateKey({
name: 'AES-GCM',
length: 256,
}, true, ['encrypt', 'decrypt'])
const encode = data => {
const encoder = new TextEncoder()
return encoder.encode(data)
}
const generateIv = () =>
window.crypto.getRandomValues(new Uint8Array(12))
const encrypt = async (data, key) => {
const encoded = encode(data)
const iv = generateIv()
const cipher = await window.crypto.subtle.encrypt({
name: 'AES-GCM',
iv
}, key, encoded)
return {
cipher,
iv
}
}
const pack = buffer => window.btoa(
String.fromCharCode.apply(null, new Uint8Array(buffer))
)
const unpack = packed => {
const string = window.atob(packed)
const buffer = new ArrayBuffer(string.length)
const bufferView = new Uint8Array(buffer)
for (let i = 0; i < string.length; i++) {
bufferView[i] = string.charCodeAt(i)
}
return buffer
}
const decode = byteStream => {
const decoder = new TextDecoder()
return decoder.decode(byteStream)
}
const decrypt = async (cipher, key, iv) => {
const encoded = await window.crypto.subtle.decrypt({
name: 'AES-GCM',
iv
}, key, cipher)
return decode(encoded)
}
Lassen Sie uns nun das Senden und Empfangen von Daten implementieren.
Wir erstellen Variablen:
// ,
const input = document.querySelector('input')
//
const output = document.querySelector('output')
//
let key
Datenverschlüsselung und Senden:
const encryptAndSendMsg = async () => {
const msg = input.value
//
key = await generateKey()
const {
cipher,
iv
} = await encrypt(msg, key)
//
await fetch('http://localhost:3000/secure-api', {
method: 'POST',
body: JSON.stringify({
cipher: pack(cipher),
iv: pack(iv)
})
})
output.innerHTML = ` <span>"${msg}"</span> .<br> .`
}
Daten empfangen und entschlüsseln:
const getAndDecryptMsg = async () => {
const res = await fetch('http://localhost:3000/secure-api')
const data = await res.json()
//
console.log(data)
//
const msg = await decrypt(unpack(data.cipher), key, unpack(data.iv))
output.innerHTML = ` .<br> <span>"${msg}"</span> .`
}
Handhabung von Tastenklicks:
document.querySelector('.btn-box').addEventListener('click', e => {
if (e.target.classList.contains('btn-send')) {
encryptAndSendMsg()
e.target.nextElementSibling.removeAttribute('disabled')
} else if (e.target.classList.contains('btn-get')) {
getAndDecryptMsg()
}
})
Starten Sie den Server für alle Fälle neu. Wir öffnen
http://localhost:3000. Klicken Sie auf die Schaltfläche "Nachricht senden":
Wir sehen die vom Server empfangenen Daten im Terminal:
{
cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',
iv: 'F8doVULJzbEQs3M1'
}
Klicken Sie auf die Schaltfläche "Nachricht abrufen":
In der Konsole werden dieselben vom Client empfangenen Daten angezeigt:
{
cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',
iv: 'F8doVULJzbEQs3M1'
}
Die Web Cryptography API eröffnet uns interessante Möglichkeiten, vertrauliche Informationen auf der Client-Seite zu schützen. Ein weiterer Schritt in Richtung serverlose Webentwicklung.
Die Unterstützung für diese Technologie beträgt derzeit 96%:
Ich hoffe, Ihnen hat der Artikel gefallen. Danke für die Aufmerksamkeit.