📑

Web Application EngineerのためのGDPR基礎入門

2024/01/05に公開

はじめに

最近、仕事で作成しているサービスがグローバル展開されることになり、個人情報を取り扱うため、GDPR(General Data Protection Regulation)の対象になり、いくらか社内監査を受けることになりました。
色々ググっても定性的で一般的な説明ばかりで、「で?どういう実装を意識すべきなの?」ということが最初にわかりませんでした。
社内の有識者に色々教えてもらい、ChatGPTの力も借りながら理解することができたので、備忘録も含めまとめを記事化します。

GDPR(一般データ保護規則)は、EU圏内の個人データの保護に関する重要な規則です。
インターネット上に公開されているサービスのアプリケーション開発者としては、これらの規則に準拠することが不可欠です。
この記事では、TypeScriptやJavaを使用したGDPR準拠のWebアプリケーション設計について、できるだけ具体的な実装例を交えながら解説したいと思います。

GDPRの基本的な要件

要件を列挙すると以下のようなことを準拠しなければならないと出てきます。

  1. データ暗号化
  2. アクセス制御
  3. データ最小化
  4. データ保護「バイ・デザイン」と「バイ・デフォルト」
  5. 同意管理
  6. データポータビリティ
  7. データ違反の対応計画
  8. レギュラーなセキュリティレビューと監査

上記トピックについてできるだけ具体的にどのようにデザインするべきかをまとめます。

データ暗号化と データ最小化

データベースレベルとアプリケーションレベルでの安全なデータ管理を意味します。例えば、ユーザーのパスワードはハッシュ化し、HTTPSを使用してデータの送受信を暗号化します。
ここでは、ユーザーパスワードの暗号化に焦点を当てます。基本的に、パスワードをデータベースに保存する際には、それを平文で保存することは避け、安全なハッシュ関数を使用してハッシュ化するのが一般的です。
SAMLやOpenIDConnectなどで、外部認証に連携している場合はサービス内の認証を意識する必要はありませんが、もしデータベースに認証用のパスワードを保存する場合は暗号化している必要があります。

基本Backendにおいて意識することなので、Javaで例を示します。

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

public class PasswordEncryption {
    
    public static String generateSalt() {
        SecureRandom random = new SecureRandom();
        byte[] salt = new byte[16];
        random.nextBytes(salt);
        return bytesToHex(salt);
    }

    public static String hashPassword(String passwordToHash, String salt) {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-512");
            md.update(salt.getBytes());
            byte[] bytes = md.digest(passwordToHash.getBytes());
            generatedPassword = bytesToHex(bytes);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return generatedPassword;
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
        }
        return sb.toString();
    }

    // メインメソッドでの使用例
    public static void main(String[] args) {
        String salt = generateSalt();
        String securePassword = hashPassword("myPassword123", salt);
        System.out.println("Salt: " + salt);
        System.out.println("Hashed Password: " + securePassword);
    }
}

このJavaクラスは、SHA-512ハッシュアルゴリズムを使用してパスワードをハッシュ化します。まず、安全なランダムな「ソルト」を生成し、このソルトをパスワードに追加してからハッシュ化します。このソルトをデータベースに保存しておくことで、ハッシュ化されたパスワードを検証する際に使用します。

PostgreSQLのテーブルにユーザーのパスワードハッシュとソルトを保存する例を示します。

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password_hash VARCHAR(128) NOT NULL,
    salt VARCHAR(32) NOT NULL
);

実装における注意点

ハッシュアルゴリズムは定期的に見直し、より安全なオプションがあれば更新する必要があります。
パスワードのハッシュ化はサーバーサイドで行い、クライアントサイド(例えば、ブラウザ)では行いません。
この例では、JavaとPostgreSQLを使用した基本的なパスワードハッシュ化の実装を示しました。実際のアプリケーションでは、セキュリティ要件やデータベース設計に応じて、さらに詳細な実装が必要になります。

ユーザーから収集するデータは、サービス提供に必要な最小限に保ちます。
不要になったデータは定期的に削除または匿名化することを検討します。
セキュリティとプライバシーを考慮したデータベース設計が重要です。(将来つかうかもという理由で利用していない個人情報は絶対に保存しない)

データ保護「バイ・デザイン」と「バイ・デフォルト」と同意管理

プライバシー設定はデフォルトで最も厳格なものに設定します。ユーザーが自分でプライバシー設定を緩和できるようにします。
データ収集ポイント(例:フォーム入力)で、ユーザーに明確な情報を提供し、同意を得ます。
ユーザー設定のデフォルト値をプライバシー保護に最適化する例をフロントエンドを想定して、Typescriptの実装をみてみます。

interface UserSettings {
    emailNotifications: boolean;
    dataSharingWithThirdParties: boolean;
    // 他の設定も可能
}

// ユーザー設定のデフォルト値
const defaultUserSettings: UserSettings = {
    emailNotifications: false, // デフォルトでオフ
    dataSharingWithThirdParties: false, // デフォルトでデータ共有しない
};

// ユーザー設定の作成または更新
function createUserSettings(settings: Partial<UserSettings>) {
    const finalSettings = {
        ...defaultUserSettings,
        ...settings,
    };
    // 設定をデータベースに保存するロジック
}

ユーザーからの同意を管理するシンプルなクラスの例です。

class ConsentManager {
    private userConsents: { [key: string]: boolean } = {};

    public giveConsent(feature: string) {
        this.userConsents[feature] = true;
    }

    public revokeConsent(feature: string) {
        this.userConsents[feature] = false;
    }

    public checkConsent(feature: string): boolean {
        return this.userConsents[feature] || false;
    }
}

// 使用例
const consentManager = new ConsentManager();
consentManager.giveConsent("emailNotifications");

データポータビリティ

ユーザーが自分の情報をダウンロードできる機能を提供します。例えば、ユーザープロフィールページに「データのエクスポート」オプションを設けます。
ユーザーデータをエクスポートする機能のtypescriptによる例です。
マイページのような専用のページにこのような機能を提供するべきとガイドされています。

class UserDataExporter {
    public async exportUserData(userId: string): Promise<string> {
        // ユーザーデータをデータベースから取得
        const userData = await getUserDataFromDatabase(userId);
        // JSON形式に変換(他の形式も可能)
        return JSON.stringify(userData);
    }
}

async function getUserDataFromDatabase(userId: string) {
    // データベースからユーザーデータを取得するロジック
    return {}; // 仮の実装
}

// 使用例
const exporter = new UserDataExporter();
const userData = await exporter.exportUserData("12345");

データ違反の対応計画とレギュラーなセキュリティレビューと監査

データ違反が発生した場合に迅速かつ効果的に対応するための計画です。

違反の検出と報告:

  • システム内での異常なアクティビティを監視し、データ違反を早期に検出するためのツールやプロセスを設置します。
  • 従業員がデータ違反を検出した際に報告するための明確なガイドラインを作成します。
    以下はデータ違反が発生した場合に通知するためのシンプルな実装です。
class DataBreachNotifier {
    public notifyBreach(breachDetails: string) {
        console.log("Data Breach:", breachDetails);
        // 通知ロジック
    }
}

やり方は他にもたくさんあると思いますが、このような施策が最低限必要になります。

ほかにも以下のような施策を検討する必要があると学びました。

初期評価と対応プロセスの実行:

違反の影響範囲や性質を特定するために、迅速な評価を実施します。
(また、必要に応じて、法執行機関や専門家に連絡します。)

  • 違反の影響を最小限に抑えるための措置を講じます。例えば、漏洩したアカウントを一時的に無効化するなどです。
    必要に応じて、影響を受けた個人や関係者に通知します。

定期的なセキュリティ監査:

  • 定期的にシステムのセキュリティ監査を実施し、脆弱性を特定します。
    監査は内部リソースまたは外部の専門家によって行われることがあります。
  • 新しいシステムやアップデートのセキュリティレビューを行います。
    コードレビュー、脆弱性スキャン、ペネトレーションテストなどを含みます。

コンプライアンスのチェック:

GDPRや他の関連するデータ保護法規に準拠しているかを定期的に確認します。
法的変更に迅速に対応するために、法的要件の更新を継続的にモニタリングします。

結論

GDPR準拠のWebアプリケーションを設計する際は、プライバシーとセキュリティを最優先に考える必要があります。この記事で紹介したJavaやTypeScriptの実装例は、
GDPRの基本的な要件を満たすための出発点として役立つと幸いです。
しかし、実際のプロジェクトでは、こんなに単純にもいかないかもしれません。
これらの概念をさらに発展させアプリケーションの特定のニーズに合わせてカスタマイズすることが重要だと思います。

GitHubで編集を提案

Discussion