このチュートリアルでは、クライアント側のデータ暗号化インターフェイスであるWeb CryptographyAPIについて説明します。このチュートリアルは、この記事に基づいています。ある程度暗号化に精通していることを前提としています。
私たちは正確に何をするつもりですか?クライアントから暗号化されたデータを受け取り、要求に応じて返す単純なサーバーを作成します。データ自体はクライアント側で処理されます。
サーバーは、JavaScriptのクライアントであるExpressを使用してNode.jsに実装されます。ブートストラップはスタイリングに使用されます。
プロジェクトコードはこちらです。
興味のある方はフォローしてください。
トレーニング
ディレクトリを作成します
crypto-tut
:
mkdir crypto-tut
それに取り組み、プロジェクトを初期化します。
cd crypto-tut
npm init -y
インストール
express
:
npm i express
インストール
nodemon
:
npm i -D nodemon
編集
package.json
:
"main": "server.js",
"scripts": {
"start": "nodemon"
},
プロジェクト構造:
crypto-tut
--node_modules
--src
--client.js
--index.html
--style.css
--package-lock.json
--package.json
--server.js
内容
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>
内容
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.js
ます。
Expressを接続し、アプリケーションとルーターのインスタンスを作成します。
const express = require('express')
const app = express()
const router = express.Router()
ミドルウェア(要求と応答の間の中間層)を接続します。
//
app.use(express.json({
type: ['application/json', 'text/plain']
}))
//
app.use(router)
//
app.use(express.static('src'))
データを格納するための変数を作成します。
let data
クライアントからの受信データを処理します。
router.post('/secure-api', (req, res) => {
//
data = req.body
//
console.log(data)
//
res.end()
})
クライアントへのデータの送信を処理します。
router.get('/secure-api', (req, res) => {
// JSON,
//
res.json(data)
})
サーバーを起動します。
app.listen(3000, () => console.log('Server ready'))
コマンドを実行し
npm start
ます。端末に「サーバー準備完了」というメッセージが表示されます。開くhttp://localhost:3000
:
これでサーバーの処理が完了し、アプリケーションのクライアント側に移動します。
クライアント
ここから楽しみが始まります。
ファイルを開きます
client.js
。
データの暗号化には、AES-GCM対称アルゴリズムが使用されます。このようなアルゴリズムでは、暗号化と復号化に同じキーを使用できます。
対称キー生成関数を作成します。
// 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'])
データは、暗号化する前にバイトストリームにエンコードする必要があります。これは、TextEncoderクラスを使用して簡単に実行できます。
// https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
const encode = data => {
const encoder = new TextEncoder()
return encoder.encode(data)
}
次に、実行ベクトル(初期化ベクトル、IV)が必要です。これは、セキュリティを強化するために暗号化キーに追加されるランダムまたは疑似ランダムの文字シーケンスです。
// 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))
ヘルパー関数を作成した後、暗号化関数を実装できます。この関数は、暗号を後でデコードできるように、暗号とIVを返す必要があります。
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
}
}
SubtleCryptoで データを暗号化した後、それらは生のバイナリデータのバッファになります。これは、送信と保存に最適な形式ではありません。これを修正しましょう。
通常、データはJSON形式で送信され、データベースに保存されます。したがって、データをポータブル形式にパックすることは理にかなっています。これを行う1つの方法は、データをbase64文字列に変換することです。
// 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))
)
データを受信した後、逆のプロセスを実行する必要があります。base64でエンコードされた文字列を生のバイナリバッファに変換します。
// 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
}
受信したデータを解読することは残っています。ただし、復号化後、バイトストリームを元の形式にデコードする必要があります。これは、TextDecoderクラスを使用して実行できます。
// https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder
const decode = byteStream => {
const decoder = new TextDecoder()
return decoder.decode(byteStream)
}
復号化機能は、暗号化機能の逆です。
// 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)
}
この段階では、コンテンツ
client.js
は次のようになります。
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)
}
それでは、データの送受信を実装しましょう。
変数を作成します。
// ,
const input = document.querySelector('input')
//
const output = document.querySelector('output')
//
let key
データの暗号化と送信:
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> .`
}
データの受信と復号化:
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> .`
}
ボタンクリックの処理:
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()
}
})
万が一に備えてサーバーを再起動してください。開き
http://localhost:3000
ます。[メッセージを送信]ボタンをクリックします。
端末でサーバーが受信したデータが表示されます。
{
cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',
iv: 'F8doVULJzbEQs3M1'
}
[メッセージを取得]ボタンをクリックします。
クライアントが受信したものと同じデータがコンソールに表示されます。
{
cipher: 'j8XqWlLIrFxyfA2easXkJTLLIt9x8zLHei/tTKI=',
iv: 'F8doVULJzbEQs3M1'
}
Web Cryptography APIは、クライアント側の機密情報を保護するための興味深い機会を提供します。サーバーレスWeb開発に向けたもう1つのステップ。
このテクノロジーのサポートは現在96%
です。この記事を楽しんでいただけたでしょうか。清聴ありがとうございました。