『暗号解読』がめちゃくちゃ面白かった

なぜか本棚にあって未読だった『暗号解読(上・下)』を読んでみました。

暗号解読(上) (新潮文庫) | サイモン シン, Singh,Simon, 薫, 青木 |本 | 通販 | Amazon

結論としては世界史、言語学、数学、物理学などさまざまな分野が統合的に組み込まれていて、めちゃくちゃ面白かったです。

暗号技術は現代において必須ですが、中身は正直難しいです。

それを歴史的な側面から「なぜ使われるようになったのか」「どうやって進歩してきたのか」「どのように作られているのか」といったことを知ることができました。

以下ざっと面白かった部分を思い出しながら書いてみます。

暗号方式

暗号には大きく2種類の方式がある

  • ステガノグラフィ
    • 最古の秘密通信手段
    • 文書自体を読めなくするのではなく、そもそも見つからないようにする
      • 伝令の髪の毛を剃って、頭に文字を書いて、伸ばす
      • カルロスゴーンがカバンに乗って出国したのも一種そうかも
  • クリプトグラフィー
    • バレてしまっても読めなければok
    • 暗号化をすること

カエサル式暗号

初期の頃の代表的な暗号として、カエサル式暗号がある

  • 小学校の頃友達と暗号を作りあって遊んでいたが、ほとんどがカエサル式暗号だった気がする
  • アルゴリズムと鍵
    • 鍵の方が圧倒的に大事ということがわかる

頻度分析

暗号解読の手法に頻度分析というものが発明された

  • 言語学的な側面も使って分析していく
  • 暗号文に一番多く出現している文字に、英語で最も多く使われる「e」を割り当てるなど
  • 単アルファベット換字式暗号はヌルを使うことで混乱させることができる

イギリスのエリザベス女王 vs スコットランドメアリー女王

暗号初期の頃に起きた「暗号大事!!」となった争いの話

  • 弱い暗号を使うくらいなら、最初から暗号など使わない方がましだ」
  • 弱い暗号は偽りの安心感を与える
  • 暗号を信頼しすぎたためまさか中身が書き換えられているとも思わず、一網打尽にされた
  • もし暗号を使っていなかったらもっと警戒したかもしれない

ヴィジュネル暗号

  • 平文中の同じ文字がその都度違う方法で暗号化される
  • 大変すぎて使われなかった

ワンタイム・パッド法

  • 理論上は最強だが実用上の欠点がある。
    • ランダムな鍵を大量に作る必要がある
    • 鍵を配送しなければならない

ヒエログリフ

ハヴァホ族

これは盲点だった 面白い発想

  • 音階や音節が違うのでそもそも聞き取れない
  • 盗聴しても聞き取れないので意味がない

無線の発達

  • 無線は全て盗聴されるので強力な暗号が必要という前提ができた

ドイツのエニグマ

  • 常に暗号解読者が暗号作成者より有利に立ってきたが、解読のモチベーションは必死さとかになるので、ドイツがエニグマを使用して難しい暗号を作った時にはイギリス、フランスの解読のモチベは下がっていた
  • ポーランドが解読していたが、連携が遅くドイツに攻め込まれる
  • 第二次世界大戦時、連合国側のチューリングがボムというものを作ってエニグマを解読、勝利に貢献した
  • 今までは言語学者が重用されていたが、これからの暗号解読には数学者が大事

RSA暗号

  • 解読者の頭文字を取ってRSAとなっている
  • 鍵配送問題を解決した
  • 双方関数ではなく、一方向関数であるの手法であるモジュロ演算に素数を混ぜ込んで使っている
  • 素因数分解は十分に大きい数であればスーパーコンピューターでも計算に何億年かかる
  • 今のところ解読はされない
  • 今のところ暗号作成者が暗号解読者より有利に立っている
    • しかし歴史上必ず暗号は解読されてきた
    • 素因数分解は今のところ総当たりで計算することになっているが、総当たりしなくてもできるようになるかもしれない
    • もしくは計算自体がめっちゃ早くなるかもしれない

量子コンピュータ

  • 概念が難しい。。僕自身きちんと理解できていない
  • 量子は「波動性」と「粒子性」の二面性を持っている
  • 古典物理学ではコンピューターは0と1だけを認識して計算を行なってきたが、量子の概念を使うと0と1の重ね合わせができる

メモ的に書き殴ってみました。 量子コンピューターなどももっと勉強してみたいなと思いました。

BigQueryに既存のCloud Firestoreのデータを同期したい

Cloud FirestoreとBigQueryを同期してデータを分析したくなりました。

qiita.com

まずこちらの記事がとても分かりやすく書かれていました。

しかし、紹介されているExport Collections to BigQuery は「変更があったドキュメント」しかBigQueryに送信してくれません。

既にDB上にあるデータもBigQuery上で分析に使いたいです。

そこで既存のデータもBigQueyに同期する方法を調べたのでメモしておきます。

一部詰まった部分があったため記事として残しておきますが、一連のやり方はこちらに書いてあります。 github.com

同期手順

gcloudツールをダウンロードしていない場合、まずはこちらからダウンロードして使えるようにします。

こちらを使ってログインをしないと後に Error importing Collection to BigQuery: Error: Could not load the default credentials.という認証系のエラーで怒られます。

ルート直下にダウンロードして、実行ファイルからloginをします。

[~]
$ ls
... google-cloud-sdk

[~]
$ ./google-cloud-sdk/bin/gcloud init

[~]
$ ./google-cloud-sdk/bin/gcloud auth application-default login

これで認証ができました。

次にbqにデータをインポートするコマンドをnpx経由で叩きます。

$ npx @firebaseextensions/fs-bq-import-collection

対話形式で回答していきます。

? What is your Firebase project ID? hogemaru-app
? What is the path of the the Cloud Firestore Collection you would like to import from? (This may, or may not, be the same Collection for which you plan to mirror changes.) posts
? Would you like to import documents via a Collection Group query? Yes
? What is the ID of the BigQuery dataset that you would like to use? (A dataset will be created if it doesn't already exist) firestore_export
? What is the identifying prefix of the BigQuery table that you would like to import to? (A table will be created if one doesn't already exist) posts
? How many documents should the import stream into BigQuery at once? 300
? Where would you like the BigQuery dataset to be located? asia-northeast1
? Would you like to run the import across multiple threads? No

これで上記の認証が済んでいればコマンドラインで指定した件数ずつインポートが開始されます(今回はデフォルトの300件ずつ)。

...
{"severity":"INFO","message":"Inserting 262 row(s) of data into BigQuery"}
{"severity":"INFO","message":"Inserted 262 row(s) of data into BigQuery"}
---------------------------------------------------------
Finished importing 10000 Firestore rows to BigQuery
---------------------------------------------------------

[~]
$

これでBigQueyで以下のようなクエリを叩いてみてください。

SELECT COUNT(document_id)
FROM POSTS;

既存のデータ量分が取得できているかと思います!

参考

BigQueryで Not found: Dataset xx:firestore_export was not found in location US のエラーが出る

Cloud Firestoreを東京リージョンで設定して、そのデータをBigQueryで使おうとしました。

クエリを書いて実行しようとすると、 Not found: Dataset xx:firestore_export was not found in location USとエラーが出てしまいました。

調べたらBigQueryのドキュメントに書いてあったのでメモしておきます。

cloud.google.com

クエリが asia-northeast1 リージョンに格納されたデータセット内のテーブルを参照する場合、クエリジョブはそのリージョンで実行されます。クエリがデータセット内のテーブルやその他のリソースを参照せず、宛先テーブルが指定されていない場合、クエリジョブは US マルチリージョンで実行されます。

まさにこれでした。

対処法

今回はweb上のSQLワークスペースでクエリを実行したいため、以下のように対応しました。

Cloud Console を使用してデータをクエリする場合は、[展開] > [クエリの設定] をクリックし、[処理を行うロケーション] で [自動選択] をクリックして、データのロケーションを選択します。

f:id:hagaahiro:20220206232737p:plain

これでデータセットもでてきてクエリも実行できるようになりました!

Auth0の認証トークンの保存方法がよくわかっていなかった

個人開発をする時にログイン周りをいつもFirebase Authenticationを使用していたため、他のIDaaSを使ってみたくなり、今回はAuth0を使ってみることにしました。

Auth0を使用していくうちに疑問が浮かんだのでメモのためにまとめておきます。

疑問

  • 許可されたAPIを叩く際にAuthorization headerで付与するトークンはどこに保持しておくのか
    • 一度ログインしたら一定期間はクライアント側に保存しておく?
    • 一回一回、認可サーバーに問い合わせてアクセストークンを取得してからサーバーにいく?

この辺りがよくわからなくなってしまったので調べてみることにしました。

環境

  • webを想定
  • auth0-reactというパッケージを使用
    • これはauth0-spa-jsをhooksのような実装にラップしたもので実際の実装はほとんどがこちらにある

github.com

また実装を追いやすいように専用のsampleアプリを作りました。 (https://github.com/haga0531/auth0-react-sample)

認証の流れ

Reactアプリ側でログインボタンを押す

auth0-reactloginWithRedirectを呼ぶようにしています。

<button onClick={() => loginWithRedirect()}>Log in</button>

loginWithRedirectの実装を見てます。

  /**
   * ```js
   * await loginWithRedirect(options);
   * ```
   *
   * Performs a redirect to `/authorize` using the parameters
   * provided as arguments. Random and secure `state` and `nonce`
   * parameters will be auto-generated.
   */
  loginWithRedirect: (options?: RedirectLoginOptions) => Promise<void>;

ここで行っていることとしては以下のようです。

  1. パラメータをセットして、 https://YOUR_DOMAIN/authorizeに移動
  2. エンドユーザーをhttps://YOUR_DOMAIN/loginの画面にリダイレクト

f:id:hagaahiro:20220110123444p:plain

またこの時のURLは以下のようになっています。 https://YOUR_DOMAIN/login?state=g6Fo2SBjNTRyanlVa3ZqeHN4d1htTnh&...

ドキュメントにも詳しく書かれていました。

ログインができ、isAuthenticatedがtrueになる

sampleアプリの実装では以下のようになっているため、yarn startを実行してlocalhostにアクセスすると、Log outボタンが表示されています。

import React from "react";
import "./App.css";
import { useAuth0 } from "@auth0/auth0-react";

function App() {
  const { isAuthenticated, loginWithRedirect, logout } = useAuth0();

  return (
    <div className="App">
      <header className="App-header">
        {!isAuthenticated ? (
          <button onClick={() => loginWithRedirect()}>Log in</button>
        ) : (
          <button onClick={() => logout()}>Log out</button>
        )}
      </header>
    </div>
  );
}

export default App;

疑問: 許可されたAPIを叩く際にAuthorization headerで付与するトークンはどこに保持しておくのか

ここで当初の疑問を思い出します。 許可されたAPIを叩く際にAuthorization headerで付与するトークンはどこに保持しておくのか

ドキュメントによるとAuth0のデフォルトの実装だとブラウザのインメモリに保存されるらしいです。

Use Auth0 SPA SDK whose default storage option is in-memory storage to leverage both Web Workers and JavaScript closures depending on the type of token.

なるほど、クライアントに保存しておくらしい。

しかし、この記述の下にこう書いてありました。

The in-memory method for browser storage does not provide persistence across page refreshes and browser tabs.

なるほど。インメモリだとリロードしたりブラウザのタブを変えたりしたら、ログイン状態が保持されないのか。

試してみます。 f:id:hagaahiro:20220110124038g:plain あれ、リロードしたもちょっとガチャってなるだけど、ログイン状態に戻った(isAuthenticatedがtrueに)。。

さらに疑問が生まれます。

Auth0のデフォルトの実装(tokenはブラウザのインメモリに保存される)のはずだけどリロードしたりブラウザ変えてもログイン状態を保持しているのはなぜだ、、

こういう時のnetworkタブということでこちらをを見てみることにしました。

見慣れないパラメータ、prompt=noneresponse_mode=web_messageを発見します。 f:id:hagaahiro:20220110124434p:plain

こちらを調べてみるとAuth0のドキュメントがヒットしました。

auth0.com

どうやらAuth0ではSilent Authenticationという仕組みでこのパラメータを使っているようです。 また以下のような記述がありました。

By default, auth0-spa-js uses the prompt=none and response_mode=web_message flow for silent auth, which depends on the user's Auth0 session. https://github.com/auth0/auth0-spa-js/blob/master/FAQ.md#using-multi-factor-authentication-mfa

response_mode=web_messageについても調べてみました。 こちらの記事がとてもわかりやすく書かれていました。

qiita.com

また例を出すが、Auth0 の SPA 用ライブラリ auth0/auth0-spa-js は Silent Authentication を使っている。初回表示時に prompt=none をつけた /authorize リクエストを裏で実行して、セッションが残っていればアプリケーションを自動的にログイン済み状態にしてくれる。

なるほど。In-memory方式で保存されているがリロードをしてもログイン状態を保持するという実装になっているのか。

なんとなく理解することができました。

Silent Authenticationにも欠点があることが述べられていますが、当初の疑問は解決された気がするのでここまでにします。

参照

React, TypeScriptでenvを読み込みたいが型がundefinedになる

create-react-app -- template typescript で作ったプロジェクトに環境変数を読み込んで使いたい時の対処法です。

下記のように環境変数を読み込ませたいのですが、この場合WEB_URLの部分はstring | undefinedと推論されます。

const hoge = process.env.WEB_URL;
// const hoge: string | undefined

process.envからコードジャンプを使って型を追っていくと、node_modules/@types/node/global.d.tsの中に、このような記述があることがわかります。

interface Dict<T> {
   [key: string]: T | undefined;
}

値が T または undefinedとなっています。

const hoge = process.env.WEB_URL || '';のようにdefault値を用意する手もありますが、今回はこちらで使われている型を拡張していきます。

create-react-appを実行したときに/src直下にreact-app-env.d.tsというファイルが追加されています。 こちらに型を書いていきます。

最終的な形はこのようになります。

/// <reference types="react-scripts" />

declare namespace NodeJS {
  interface ProcessEnv {
    readonly REACT_APP_WEB_URL: string;
  }
}

prefixとしてREACT_APP_環境変数につける必要があります。

create-react-app.dev

.envの中身は下記のようになります。

REACT_APP_WEB_URL=https://example.com

これで最初に書いた方がstringと推論されるようになりました。

const hoge = process.env.WEB_URL;
// const hoge: string;

余談 react-app-env.d.tsについて

react-app-env.d.tsですが、init直後はこのようになっていました。

/// <reference types="react-scripts" />

この記述はTypeScriptのTriple-Slash Directivesというもので、

何種類かありますが、今回使用されているのは /// <reference types="..." />型ですね。

ドキュメントには下記のように書かれています。

a /// directive declares a dependency on a package. ... For example, including /// in a declaration file declares that this file uses names declared in @types/node/index.d.ts;

今回の例だと、/// <reference types="react-scripts" />とあるので、react-app-env.d.ts内で node_modules/react-scripts/lib/react-app.d.tsで宣言されている名前を使用することを表しています。

この記事の最初に記述したようなコードと同じようなことをnode_modules/react-script/lib/react-app.d.tsの中でも行っていることがわかります。

declare namespace NodeJS {
  interface ProcessEnv {
    readonly NODE_ENV: 'development' | 'production' | 'test';
    readonly PUBLIC_URL: string;
  }
}

このために、vscodeなどのエディターでは最初から型補完が効いているみたいですね。

f:id:hagaahiro:20220106224743p:plain

sveltekitで SyntaxError: The requested module '/hoge' does not provide an export named 'Hoge' がでる

svelteに入門し、早速フレームワークのsveltekitを試してみました。

外部ファイルである dummy-data.tsindex.svelte にimportしようとした時に

SyntaxError: The requested module '/hoge' does not provide an export named 'Hoge'

というエラーが出て少しハマったので解決策を書いておきます。

エラーとなる状態

dummy-data.tsを作ります。

export type Data = {
  id: number;
  title: string;
}

export const dummyData: Data[]  = [
  {
    id: 1,
    title: 'hogehoge',
  },
  {
    id: 2,
    title: 'fuga',
  }
]

上記のようなTypeScriptファイルをsveleteにimportしようとしました。

index.svelte

<script lang="ts">
  import {Data, dummyData} from '../../dummy-data';

...
</script>

これでリロードをすると画面上に SyntaxError: The requested module '/dummy-data.ts' does not provide an export named 'Data' というエラーが表示されます。

解決策

どうやらsveltekitで使用されているビルドツールのviteに固有のエラーだそうです。 GitHub上にissueが上がっていました。

github.com

これによると

import type { User } from '~/types'

このような形式でimportするとうまくいくようです。

index.svelteを下記のように書き換えます。

<script lang="ts">
    import type {Data} from '../../dummy-data';
    import {dummyData} from '../../dummy-data';
...
</script>

これでビルドがうまくいきました!! ただ同じファイルからimportしているのに分けて記述するのは面倒ですね。 他に解決策がありましたら教えてください🙇‍♀️

graphql-codegenをAWS AppSync, TypeScriptでやりたい

AWSでGraphQLを使う際のサービス、AWS AppSyncにcodegenを導入する際に詰まったのでメモ。 基本的なコマンドやドキュメントはこちらを見ます。 なお、私はpackage managerはyarnが好きなのでyarnを使用します。npmでもやることは特に変わりません。

www.graphql-code-generator.com

codegenをinstall

まずはgraphqlをインストール。

$ yarn add graphql

devにcodegenをインストール。

$ yarn add -D @graphql-codegen/cli

初期ファイルをセットアップ

対話形式でセットアップします。 後で修正する部分もあるのでとりあえず下記のように初期セットアップ。

$ yarn graphql-codegen init
yarn run v1.22.10
$ /Users/xxxxx/yyyy/node_modules/.bin/graphql-codegen init

    Welcome to GraphQL Code Generator!
    Answer few questions and we will setup everything for you.
  
? What type of application are you building? Backend - API or server
? Where is your schema?: (path or url) http://localhost:4000
? Pick plugins: TypeScript (required by other typescript plugins)
? Where to write the output: src/generated/graphql.ts
? Do you want to generate an introspection file? Yes
? How to name the config file? codegen.yml
? What script in package.json should run the codegen? codegen
Fetching latest versions of selected plugins...

    Config file generated at codegen.yml
    
      $ npm install

    To install the plugins.

      $ npm run codegen

    To run GraphQL Code Generator.
  
✨  Done in 82.03s.

TypeScriptも使いたいのでインストールします。

$ yarn add -D @graphql-codegen/typescript

initをしたらcodegen.ymlというファイルが生成されます。

対話形式で選択したような感じに初期セットアップがされていますが、私は下記のように書き換えました。 - schemaを指定する - 不必要な部分を削除

overwrite: true #上書きを許可する
schema:
  - ./schema.graphql 
  - ./schema/**/*.graphql # コード量が多くなってきたときに備えてグローバル分割しておく


generates:
  ./src/generated/graphql.ts:
    plugins:
      - "typescript"

package.jsonのscriptsを少し修正します。

"scripts": {
    ...
    "codegen": "yarn graphql-codegen"
 },

任意のschemaを記述します。

こちらは後々のために最小限の記述にしておきます。

schema.graphql

schema {
  query: Query
  mutation: Mutation
}

適当なschemaを記述します。

schema/hoge.graphql

type Query {
  getHoge: [Hoge!]
}

type Hoge {
  id: ID
  createdAt: AWSDateTime
  title: String!
  description: String!
}
...

これでyarn codegenというコマンドを実行すればschemaが自動生成されるはずのつもりでした。

Type "AWSDateTime" not found in document. Error: Type "AWSDateTime" not found in document.

yarn codegenを実行すると下記のエラーが出てしまいました。

$ yarn codegen

...
src/generated/graphql.ts
    Failed to load schema from ./schema.graphql,./schema/**/*.graphql:

        Type "AWSDateTime" not found in document.
        Error: Type "AWSDateTime" not found in document.


https://benoitboure.com/how-to-use-typescript-with-appsync-lambda-resolvers

AWS AppSync特有のプロパティであるAWSDateTimeを使用しているため対応が必要でした。

schema以下に新たなファイルを追加します。

schema/appsync.graphql

scalar AWSDateTime

codegen.ymlにも追記が必要です。

schema:
  - ./schema.graphql
  - ./schema/**/*.graphql

# 以下を追記
config: 
  scalars:
    AWSDateTime: string

これでもう一度 yarn codegenを実行します。

エラー内容が変わりました。。。

Error: Cannot use GraphQLObjectType "Query" from another module or realm.

$ yarn codegen
...

Error: Cannot use GraphQLObjectType "Query" from another module or realm.

    Ensure that there is only one instance of "graphql" in the node_modules
    directory. If different versions of "graphql" are the dependencies of other
    relied on modules, use "resolutions" to ensure only one version is installed.

    https://yarnpkg.com/en/docs/selective-version-resolutions

    Duplicate "graphql" modules cannot be used at the same time since different
    versions may have different capabilities and behavior. The data from one
    version used in the function from another could produce confusing and
    spurious results.

こちらはターミナルにきちんと載っていました。 graphqlのバージョンが複数ある問題でした。

Selective dependency resolutions | Yarn

ドキュメントに従ってpackage.jsonに追記します。

"resolutions": {
    "graphql": "^15.5.0"
  },

依存をきちんとインストールしてあげます。

$ yarn

もう一度codegen

$ yarn codegen
yarn run v1.22.10
$ yarn graphql-codegen
$ /Users/xxxx/yyy/node_modules/.bin/graphql-codegen
  ✔ Parse configuration
  ✔ Generate outputs
✨  Done in 1.29s.

できました!