メインコンテンツまでスキップ

lesson-1_ノートを暗号化しよう

🔐 ノートを暗号化する

前回のセクションで、アプリケーションの要となる対称鍵の同期処理が完成しました。このレッスンでは、対称鍵を使用してノートを暗号化します。

それでは実際に、ノートを暗号化するコードを書いていきましょう。

lib/cryptoService.ts内に定義されているencryptNote関数を下記の内容に更新します。

  public async encryptNote(data: string): Promise<string> {
if (this.symmetricKey === null) {
throw new Error('Not found symmetric key');
}

// 12バイトのIV(初期化ベクター)を生成します。
// // 同じ鍵で繰り返し暗号化を行う際に、それぞれの暗号文が同じにならないようにするためです。
const iv = window.crypto.getRandomValues(
new Uint8Array(CryptoService.INIT_VECTOR_LENGTH),
);

// ノートをUTF-8のバイト配列に変換します。
const encodedNote: Uint8Array = new TextEncoder().encode(data);

// 対称鍵を使ってノートを暗号化します。
const encryptedNote: ArrayBuffer = await window.crypto.subtle.encrypt(
// 使用するアルゴリズム
{
name: 'AES-GCM',
iv,
},
// 使用する鍵
this.symmetricKey,
// 暗号化するデータ
encodedNote,
);

// テキストデータとIVを結合します。
// // IVは、復号時に再度使う必要があるためです。
const decodedIv: string = this.arrayBufferToBase64(iv);
const decodedEncryptedNote: string =
this.arrayBufferToBase64(encryptedNote);

return decodedIv + decodedEncryptedNote;
}

コードを確認しましょう。

ノートの暗号化を行う前に、crypto.getRandomValuesメソッドを使用して、乱数を生成します。乱数は、8ビットの整数値(0 ~ 255)が12個生成されます。この乱数はノートの暗号化に用いられるもので、同じ鍵で繰り返し暗号化を行う際に、同じデータが同じ暗号文とならないようにしてくれます。

// 12バイトのIV(初期化ベクター)を生成します。
// // 同じ鍵で繰り返し暗号化を行う際に、それぞれの暗号文が同じにならないようにするためです。
const iv = window.crypto.getRandomValues(
new Uint8Array(CryptoService.INIT_VECTOR_LENGTH)
);

暗号化には、subtle.encryptメソッドを使用します。

// 対称鍵を使ってノートを暗号化します。
const encryptedNote: ArrayBuffer = await window.crypto.subtle.encrypt(
// 使用するアルゴリズム
{
name: "AES-GCM",
iv,
},
// 暗号化に使用する鍵
this.symmetricKey,
// 暗号化するデータ
encodedNote
);

iv(乱数の配列)は暗号文の復号に必要なため、暗号文とivを結合したものをバックエンドキャニスターに保存する必要があります。そのために、ivと暗号文を結合したものを戻り値とします。

// テキストデータとIVを結合します。
// // IVは、復号時に再度使う必要があるためです。
const decodedIv: string = this.arrayBufferToBase64(iv);
const decodedEncryptedNote: string = this.arrayBufferToBase64(encryptedNote);

return decodedIv + decodedEncryptedNote;

これで、ノートの暗号化を行うencryptNote関数が完成しました。では、この関数をコンポーネントから呼び出してみましょう。ノートを暗号化するタイミングは、ユーザーがノートを追加・更新するときです。

まずは、routes/notes/index.tsxのNotesコンポーネントに定義されたaddNote関数を下記のように更新します。

const addNote = async () => {
if (auth.status !== "SYNCED") {
console.error(`CryptoService is not synced.`);
return;
}

setIsLoading(true);

try {
// ノートの暗号化を行います。
const encryptedNote = await auth.cryptoService.encryptNote(
currentNote.data
);
await auth.actor.addNote(encryptedNote);
await getNotes();
} catch (err) {
showMessage({
title: "Failed to add note",
status: "error",
});
} finally {
onCloseNoteModal();
setIsLoading(false);
}
};

続いて、updateNote関数を更新します。

const updateNote = async () => {
if (auth.status !== "SYNCED") {
console.error(`CryptoService is not synced.`);
return;
}

setIsLoading(true);

try {
// ノートの暗号化を行います。
const encryptedData = await auth.cryptoService.encryptNote(
currentNote.data
);
const encryptedNote = {
id: currentNote.id,
data: encryptedData,
};
await auth.actor.updateNote(encryptedNote);
await getNotes();
} catch (err) {
showMessage({
title: "Failed to update note",
status: "error",
});
} finally {
onCloseNoteModal();
setIsLoading(false);
}
};

暗号化の機能が完成しました!

✅ 動作確認を使用

実際にノートを追加・更新してみましょう。入力したテキストが暗号化されていることが確認できます!

🙋‍♂️ 質問する

ここまでの作業で何かわからないことがある場合は、Discordの#icpで質問をしてください。

ヘルプをするときのフローが円滑になるので、エラーレポートには下記の4点を記載してください ✨

1. 質問が関連しているセクション番号とレッスン番号
2. 何をしようとしていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット

次のレッスンに進み、ノートの復号を行いましょう!