Tech note

備忘録

Nuxtで暗号化した値をブラウザのJavaScriptで復号化して再度暗号化する

環境

Nuxt
名称 Version
Node 14.5.0
Nuxt 2.13.3
Vue 2.6.11
TypeScript 3.8.3
Vuetify 2.3.3
nuxt-property-decorator 2.7.2
ブラウザのJavaScript
名称 Version
jQuery 3.5.1
Bootstrap 4.5.2

使用する拡張機能

Nuxt

crypto

Nuxtで暗号化する方法

const S: string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
const numArr32: number[] = Array.from(crypto.randomFillSync(new Uint8Array(32)));
const numArr16: number[] = Array.from(crypto.randomFillSync(new Uint8Array(16)));
const keyValue: string = numArr32.map((n: number)=>S[n%S.length]).join("");
const ivValue: string = numArr16.map((n: number)=>S[n%S.length]).join("");
const key: Uint8Array = new Buffer(keyValue, "utf-8");
const iv: Uint8Array = new Buffer(ivValue, "utf-8");
const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
let encrypted = cipher.update(暗号化したい値);
encrypted += cipher.final("hex");
備考

keyValueとivValueは、ランダムな文字列を生成して格納。

ブラウザのJavaScriptで復号化する方法

const inputIv = Nuxtで暗号化した値;
const inputKey = Nuxtで暗号化の際に使用したkeyValueの値;
const inputIv = Nuxtで暗号化の際に使用したivValueの値;
const key = new TextEncoder("utf-8").encode(inputKey);
const iv = new TextEncoder("utf-8").encode(inputIv);
const convertUint8Arr = new Uint8Array(inputVal.match(/.{1,2}/g).map(v => parseInt(v, 16)));
window.crypto.subtle.importKey(
  "raw",
  key,
  "AES-CBC",
  true,
  ["decrypt"]
).then(key => {
  window.crypto.subtle.decrypt(
    {
      name: "AES-CBC",
      iv
    },
    key,
    convertUint8Arr
  ).then(element => {
    const decryptoVal = new TextDecoder("utf-8").decode(element);
  })
})

ブラウザのJavaScriptで再度暗号化する方法

const inputVal = 暗号化したい値;
const inputKey = Nuxtで暗号化の際に使用したkeyValueの値;
const inputIv = Nuxtで暗号化の際に使用したivValueの値;
const key = new TextEncoder("utf-8").encode(inputKey);
const iv = new TextEncoder("utf-8").encode(inputIv);
window.crypto.subtle.importKey(
  "raw",
  key,
  "AES-CBC",
  true,
  ["encrypt"]
).then(key => {
  window.crypto.subtle.encrypt(
    {
      name: "AES-CBC",
      iv
    },
    key,
    new TextEncoder("utf-8").encode(inputVal)
  ).then(element => {
    const cryptoVal = Array.from(new Uint8Array(element)).map(num => num.toString(16).padStart(2, "0")).join("");
  })
})

作成した画面のスクリーンショット

Nuxt

f:id:sbu8:20200822195652p:plain

ブラウザのJavaScript

f:id:sbu8:20200822195825p:plain

全体のソースコード

Nuxt
<template>
  <div>
    <v-text-field
      v-model="inputCrypto"
      label="暗号化したい文字列"
    >
    </v-text-field>
    <v-btn @click="convertCrypto">暗号化</v-btn>
    <div>暗号化: {{ outputCrypto }}</div>
    <div>Key: {{ useCryptoKeyValue }}</div>
    <div>Initial Vector: {{ useCryptoIvValue }}</div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from "nuxt-property-decorator";
const crypto = require("crypto");

@Component({})
export default class extends Vue {
  private inputCrypto: string = "";
  private outputCrypto: string = "";
  private useCryptoKeyValue: string = "";
  private useCryptoIvValue: string = "";

  private convertCrypto(): void {
    const S: string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    const numArr32: number[] = Array.from(crypto.randomFillSync(new Uint8Array(32)));
    const numArr16: number[] = Array.from(crypto.randomFillSync(new Uint8Array(16)));
    const keyValue: string = numArr32.map((n: number)=>S[n%S.length]).join("");
    const ivValue: string = numArr16.map((n: number)=>S[n%S.length]).join("");
    const key: Uint8Array = new Buffer(keyValue, "utf-8");
    const iv: Uint8Array = new Buffer(ivValue, "utf-8");
    const cipher = crypto.createCipheriv("aes-256-cbc", key, iv);
    let encrypted = cipher.update(this.inputCrypto);
    encrypted += cipher.final("hex");
    this.useCryptoKeyValue = keyValue;
    this.useCryptoIvValue = ivValue;
    this.outputCrypto = encrypted;
  }
}
</script>
ブラウザのJavaScript
html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
    integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
  <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
    integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj"
    crossorigin="anonymous"></script>
  <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
    integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
    crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"
    integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV"
    crossorigin="anonymous"></script>
  <script type="text/javascript" src="./js/decrypto.js" defer="defer"></script>
  <title>復号化</title>
</head>
<body>
  <div class="container">
    <div class="form-group">
      <label for="cryptoVal">暗号化した値</label>
      <input type="text" id="cryptoVal" class="form-control">
    </div>
    <div class="form-group">
      <label for="cryptoKey">Key</label>
      <input type="text" id="cryptoKey" class="form-control">
    </div>
    <div class="form-group">
      <label for="cryptoIv">Initial Vector</label>
      <input type="text" id="cryptoIv" class="form-control">
    </div>
    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <button id="decryptoBtn" class="btn btn-primary">復号化</button>
      </div>
    </div>
    <div class="form-group">
      <label id="outputDecrypto" class="col-sm-2 control-label"></label>
    </div>
    <div class="form-group">
      <label for="cryptoVal">暗号化する値</label>
      <input type="text" id="cryptoVal2" class="form-control">
    </div>
    <div class="form-group">
      <label for="cryptoKey">Key</label>
      <input type="text" id="cryptoKey2" class="form-control">
    </div>
    <div class="form-group">
      <label for="cryptoIv">Initial Vector</label>
      <input type="text" id="cryptoIv2" class="form-control">
    </div>
    <div class="form-group">
      <div class="col-sm-offset-2 col-sm-10">
        <button id="cryptoBtn" class="btn btn-primary">暗号化</button>
      </div>
    </div>
    <div class="form-group">
      <label id="outputCrypto" class="col-sm-2 control-label"></label>
    </div>
  </div>
</body>
</html>
JavaScript
$("#decryptoBtn").on("click", function() {
  const inputVal = $("#cryptoVal").val();
  const inputKey = $("#cryptoKey").val();
  const inputIv = $("#cryptoIv").val();
  const key = new TextEncoder("utf-8").encode(inputKey);
  const iv = new TextEncoder("utf-8").encode(inputIv);
  const convertUint8Arr = new Uint8Array(inputVal.match(/.{1,2}/g).map(v => parseInt(v, 16)));
  window.crypto.subtle.importKey(
    "raw",
    key,
    "AES-CBC",
    true,
    ["decrypt"]
  ).then(key => {
    window.crypto.subtle.decrypt(
      {
        name: "AES-CBC",
        iv
      },
      key,
      convertUint8Arr
    ).then(element => {
      const decryptoVal = new TextDecoder("utf-8").decode(element);
      $("#outputDecrypto").text(decryptoVal)
    })
  })
});

$("#cryptoBtn").on("click", function() {
  const inputVal = $("#cryptoVal2").val();
  const inputKey = $("#cryptoKey2").val();
  const inputIv = $("#cryptoIv2").val();
  const key = new TextEncoder("utf-8").encode(inputKey);
  const iv = new TextEncoder("utf-8").encode(inputIv);
  window.crypto.subtle.importKey(
    "raw",
    key,
    "AES-CBC",
    true,
    ["encrypt"]
  ).then(key => {
    window.crypto.subtle.encrypt(
      {
        name: "AES-CBC",
        iv
      },
      key,
      new TextEncoder("utf-8").encode(inputVal)
    ).then(element => {
      const cryptoVal = Array.from(new Uint8Array(element)).map(num => num.toString(16).padStart(2, "0")).join("");
      $("#outputCrypto").text(cryptoVal)
    })
  })
});