0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ひとりJavaScriptAdvent Calendar 2024

Day 25

Web Crypto API(SubtleCrypto)って何?文字列を暗号化してみる

Last updated at Posted at 2024-12-24

この記事は暗号化に全く詳しくない人が書いています。
遊びで使うなら問題ないと思いますが、実用する際は決して内容を鵜呑みにせず、専門家を頼ってください。

Web Crypto APIとは

Web Crypto APIは、ざっくり言うとWeb上で簡単な暗号化ができるAPIです。

これにはwindow.cryptoからアクセスできます。
window.cryptoCryptoインターフェースのオブジェクトになっていて、randomUUIDメソッドやsubtleプロパティを持っています。

この記事では、subtleプロパティに入っているSubtleCryptoに焦点を当てて解説します。

SubtleCrypto

SubtleCryptoは、実際に暗号化などの処理が定義されているインターフェースです。
window.crypto.subtleから利用できます。

ここでは以下の処理を紹介します。

  • 鍵の生成
  • 文字列の暗号化 / 復号

ここではRSA-OAEPというアルゴリズムに絞って紹介します。

RSA-OAEP公開鍵暗号と呼ばれる暗号方式のアルゴリズムです。
公開鍵暗号には公開鍵と秘密鍵があり、公開鍵で文書を暗号化し、それを秘密鍵で復号します。

公開鍵暗号の詳細はこちらがわかりやすいと思います。

鍵の生成

文字列を暗号化したり復号するにはが必要です。
鍵の生成にはgenerateKeyを使用します。

generateKey関数はCryptoKeyPairオブジェクトで履行されるプロミスを返します。
CryptoKeyPairは公開鍵と秘密鍵のペアで、以下のプロパティを持っています。

  • publickey: 公開鍵を示すCryptoKeyオブジェクト
  • privateKey: 秘密鍵を示すCryptoKeyオブジェクト

使ってみる

generateKeyは以下の引数を取ります。

  1. algorithm: 鍵のアルゴリズム
  2. extractable: 鍵をエクスポートできるかどうか、今回はtrue
  3. keyUsages: 生成した鍵で何ができるかを入れる配列
    • algorithmに対応していない文字列を入れるとエラーになる
    • RSA-OAEPencrypt(暗号化)とdecrypt(復号)ができる

RSA-OAEPの場合、1のalgorithmにはRsaHashedKeyGenParamsオブジェクトを入れます。

例えば以下のように呼び出せます。

// RsaHashedKeyGenParamsオブジェクト
const algorithm = {
  name: 'RSA-OAEP', // アルゴリズムの名前
  modulusLength: 4096, // 法(?)の長さ
  publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 公開指数(?)
  hash: 'SHA-256' // ダイジェスト関数に使うアルゴリズム名
}

// generateKeyで鍵を生成する
const keyPair = await window.crypto.subtle.generateKey(
  algorithm,
  true, // エクスポートできるようにする
  ['encrypt', 'decrypt'] // 暗号化と復号に使う
)

keyPair.publicKey // CryptoKey
keyPair.privateKey // CryptoKey

暗号化 / 復号

ここからはencryptdecrypt関数を使って、実際に文字列を暗号化したり復号する処理を書いていきます。

暗号化

暗号化にはencrypt関数を使います。
encryptを使うと、暗号化する前の文章(平文)であるArrayBufferから、暗号化したArrayBufferを取得できます。

この関数は以下の引数を取ります。

  1. algorithm: ここではRsaOaepParamsオブジェクト
  2. key: 暗号化に使う公開鍵のCryptoKeyオブジェクト
  3. data: 暗号化したいArrayBuffer

暗号化したい文字列をArrayBufferに変換するにはTextEncoderが使えます。

// RSA-OAEPの鍵を生成する
const keyPair = await window.crypto.subtle.generateKey(/* ... */)

// 暗号化したいデータ
const text = 'Hello World!'
const textBuffer = new TextEncoder().encode(text)

// encryptで暗号化する
const cipherBuf = await window.crypto.subtle.encrypt(
  { name: 'RSA-OAEP' }, // algorithm
  keyPair.publicKey,
  textBuffer
)

cipherBuf // 暗号化されたArrayBuffer

base64arrayBufferを変換する

暗号化したArrayBufferが生成できたところで、次はこれをBase64文字列に変換します。
これはバイナリのデータをASCII文字列にする感じです。

といっても現在のWeb APIには直接変換する方法がないので、変換する関数を自分で定義します。
ここでは、こちらの記事のコードから型定義を取って使うことにします。

arrayBufferToBase64: ArrayBufferをBase64文字列に変換する関数

function arrayBufferToBase64(buffer) {
  const str = String.fromCharCode.apply(
    null,
    new Uint8Array(buffer),
  )

  return window.btoa(str)
}

// 呼び出し例
const cipherBuf = await window.crypto.subtle.encrypt(/* ... */)
arrayBufferToBase64(cipherBuf) // 例: WPlIHJOKjTmzxq...

base64ToArrayBuffer: Base64文字列をArrayBufferに変換する関数

function base64ToArrayBuffer(base64) {
  const str = window.atob(base64)
  const buf = new ArrayBuffer(str.length)
  const bufView = new Uint8Array(buf)
  for (let i = 0, strLen = str.length; i < strLen; i++) {
    bufView[i] = str.charCodeAt(i)
  }
  return buf
}

// 呼び出し例
const cipherText = // 暗号文のBase64文字列
base64ToArrayBuffer(cipherText) // ArrayBuffer

復号

暗号化されたArrayBufferを復号するにはdecrypt関数を使います。

decryptは以下の引数を取ります。

  • algorithm: RsaOaepParamsオブジェクト
  • key: 復号に使用する秘密鍵を示すCryptoKey
  • data: 暗号化されているArrayBuffer

例えば、以下のように呼び出せます。

// RSA-OAEPの鍵
const keyPair = // ...

// keyPairの公開鍵で暗号化されたArrayBuffer
const cipherBuf = base64ToArrayBuffer(/* ... */)

// 復号
const textBuf = await window.crypto.subtle.decrypt(
  { name: 'RSA-OAEP' },
  keyPair.privateKey,
  cipherBuf
) // ArrayBuffer

通しで呼び出す

ということで、ここまでの関数を通しで呼び出してみようと思います。

とその前に、ここで一度、平文の文字列から暗号文(Base64)ができるまで、そして暗号文が平文に復号されるまでの過程を整理します。

これは大まかに以下のシーケンス図のようになります。

ということで、次はこれをコードにしてみます。
コードでは以下のことをやってみます。

  1. 鍵を生成する
  2. 文字列を一度暗号化する
  3. 2の文字列を復号して出力する

実際のコードはこちらです。

準備
// 鍵の生成に使うアルゴリズム
const keyGenAlgorithm = {
  name: 'RSA-OAEP',
  modulusLength: 4096,
  publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
  hash: 'SHA-256',
}

// 暗号化や復号に使うアルゴリズム
const encryptAlgorithm = { name: 'RSA-OAEP' }

// 鍵を生成する
const keyPair = await window.crypto.subtle.generateKey(
  keyGenAlgorithm,
  true, // エクスポートできるようにする
  ['encrypt', 'decrypt'] // 暗号化と復号ができるようにする
)
暗号化
// 0. 暗号化したい文章(平文)
const text = 'Hello Crypto!'

async function encrypt(text) {
  // 1. 平文をArrayBufferに変換
  const textBuf = new TextEncoder().encode(text)

  // 2. 暗号化
  const cipherBuf = await window.crypto.subtle.encrypt(
    encryptAlgorithm,
    keyPair.publicKey,
    textBuf
  )

  // 3. cipherBufをBase64文字列に変換
  const cipherBase64 = arrayBufferToBase64(cipherBuf)
  return cipherBase64
}

const cipher = await encrypt(text) // XwZXGbI7Inopw...
復号
// 0. 復号したいBase64文字列
const cipher = await encrypt(text)

async function decrypt(cipherBase64) {
  // 1. 暗号文をArrayBufferに変換
  const cipherBuf = base64ToArrayBuffer(cipherBase64)

  // 2. 復号
  const textBuf = await window.crypto.subtle.decrypt(
    encryptAlgorithm,
    keyPair.privateKey,
    cipherBuf
  )

  // 3. textBufを文字列に変換
  const text = new TextDecoder().decode(textBuf)
  return text
}

const text2 = await decrypt(cipher)
console.log(text2) // 'Hello Crypto!'
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?