Chromeのクッキーを復号化してみた (macos版)

こんにちは!今回は、macOS上でChromeブラウザのクッキーを復号化するTypeScriptスクリプトについて解説します。このスクリプトは、ブラウザのセキュリティメカニズムを理解するための教育目的で作成されたものです。

はじめに

ブラウザのクッキーには、ユーザーのセッション情報やプリファレンスなど、重要なデータが含まれています。Chromeは、これらの情報を保護するために暗号化を使用しています。今回紹介するスクリプトは、この暗号化されたデータを復号化する方法を示しています。

注意: このスクリプトは教育目的でのみ使用してください。他人のデータにアクセスすることは違法であり、プライバシーの侵害になる可能性があります。

スクリプトの概要

このスクリプトは以下の主要な機能を持っています:

  1. Chromeの暗号化キーの取得

  2. クッキーデータベースの検索

  3. 暗号化されたクッキーの復号化

それでは、各部分を詳しく見ていきましょう。

1. 暗号化キーの取得

const getEncryptionKey = async (): Promise<Buffer> => {
  try {
    const stdout = execSync(
      "security find-generic-password -w -a 'Chrome' -s 'Chrome Safe Storage'",
      { encoding: 'utf-8' }
    );
    console.log('Successfully retrieved Chrome Safe Storage key');
    const rawKey = Buffer.from(stdout.trim(), 'utf-8');
    const key = crypto.pbkdf2Sync(rawKey, 'saltysalt', 1003, 16, 'sha1');
    return key;
  } catch (error) {
    console.error('Failed to retrieve Chrome Safe Storage key automatically:', error);
    return await getManualEncryptionKey();
  }
};

この関数は、macOSのキーチェーンからChromeの暗号化キーを取得します。取得したキーは、PBKDF2アルゴリズムを使用して16バイトのキーに変換されます。

2. クッキーデータベースの検索

const findChromeProfilePath = (): string | null => {
  const homeDir = process.env.HOME || process.env.USERPROFILE;
  if (!homeDir) {
    console.error('Unable to determine home directory');
    return null;
  }

  const possiblePaths = [
    path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'Default'),
    path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'Profile 1'),
    path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'Profile 2'),
  ];

  for (const profilePath of possiblePaths) {
    const cookieFilePath = path.join(profilePath, 'Cookies');
    if (fs.existsSync(cookieFilePath)) {
      console.log(`Found Chrome Cookies file at: ${cookieFilePath}`);
      return cookieFilePath;
    }
  }

  console.error('Chrome Cookies file not found in any of the expected locations');
  return null;
};

この関数は、Chromeのプロファイルディレクトリを検索し、クッキーデータベースファイルの場所を特定します。

3. クッキーの復号化

const decryptCookie = (encryptedValue: Buffer, encryptionKey: Buffer): string => {
  // Remove the 'v10' prefix
  encryptedValue = encryptedValue.slice(3);

  const initVector = Buffer.alloc(16, ' ');
  const decipher = crypto.createDecipheriv('aes-128-cbc', encryptionKey, initVector);
  decipher.setAutoPadding(false);

  let decrypted = decipher.update(encryptedValue);
  decrypted = Buffer.concat([decrypted, decipher.final()]);

  // Remove padding
  const padding = decrypted[decrypted.length - 1];
  decrypted = decrypted.slice(0, decrypted.length - padding);

  return decrypted.toString('utf-8');
};

この関数は、暗号化されたクッキー値を復号化します。AES-128-CBCアルゴリズムを使用し、パディングを手動で処理しています。

スクリプトの使用方法

必要なパッケージをインストールします:

npm install typescript ts-node @types/node sqlite3 @types/sqlite3

スクリプトを実行します:

ts-node chrome_cookie_decryptor.ts

注意点

  • このスクリプトは教育目的でのみ使用してください。

  • 他人のデータにアクセスすることは違法であり、避けるべきです。

  • Chromeのバージョンアップデートにより、暗号化方式が変更される可能性があります。その場合、このスクリプトは更新が必要になるかもしれません。

まとめ

このスクリプトを通じて、ブラウザのセキュリティメカニズムの一端を垣間見ることができました。クッキーの暗号化や保護の重要性を理解し、ウェブ開発やセキュリティに関する知識を深める良い機会になれば幸いです。

セキュリティとプライバシーは常に重要なトピックです。このような知識を適切に活用し、より安全なウェブ環境の構築に貢献していきましょう。

フルコード

import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';
import * as sqlite3 from 'sqlite3';
import { promisify } from 'util';
import { execSync } from 'child_process';
import * as readline from 'readline';

interface Cookie {
  host_key: string;
  name: string;
  encrypted_value: Buffer;
}

const getEncryptionKey = async (): Promise<Buffer> => {
  try {
    const stdout = execSync(
      "security find-generic-password -w -a 'Chrome' -s 'Chrome Safe Storage'",
      { encoding: 'utf-8' }
    );
    console.log('Successfully retrieved Chrome Safe Storage key');
    const rawKey = Buffer.from(stdout.trim(), 'utf-8');
    const key = crypto.pbkdf2Sync(rawKey, 'saltysalt', 1003, 16, 'sha1');
    return key;
  } catch (error) {
    console.error('Failed to retrieve Chrome Safe Storage key automatically:', error);
    return await getManualEncryptionKey();
  }
};

const getManualEncryptionKey = async (): Promise<Buffer> => {
  console.log('Please try to retrieve the key manually:');
  console.log('1. Open Keychain Access');
  console.log('2. Search for "Chrome Safe Storage"');
  console.log('3. Double click on the item');
  console.log('4. Check "Show password"');
  console.log('5. Copy the password');
  
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });

  const manualKey = await new Promise<string>((resolve) => {
    rl.question('Please enter the Chrome Safe Storage key manually (or press Enter to use a default method): ', (answer) => {
      rl.close();
      resolve(answer);
    });
  });

  const rawKey = Buffer.from(manualKey || 'peanuts', 'utf-8');
  const key = crypto.pbkdf2Sync(rawKey, 'saltysalt', 1003, 16, 'sha1');
  return key;
};

const decryptCookie = (encryptedValue: Buffer, encryptionKey: Buffer): string => {
  // Remove the 'v10' prefix
  encryptedValue = encryptedValue.slice(3);

  const initVector = Buffer.alloc(16, ' ');
  const decipher = crypto.createDecipheriv('aes-128-cbc', encryptionKey, initVector);
  decipher.setAutoPadding(false);

  let decrypted = decipher.update(encryptedValue);
  decrypted = Buffer.concat([decrypted, decipher.final()]);

  // Remove padding
  const padding = decrypted[decrypted.length - 1];
  decrypted = decrypted.slice(0, decrypted.length - padding);

  return decrypted.toString('utf-8');
};

const findChromeProfilePath = (): string | null => {
  const homeDir = process.env.HOME || process.env.USERPROFILE;
  if (!homeDir) {
    console.error('Unable to determine home directory');
    return null;
  }

  const possiblePaths = [
    path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'Default'),
    path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'Profile 1'),
    path.join(homeDir, 'Library', 'Application Support', 'Google', 'Chrome', 'Profile 2'),
  ];

  for (const profilePath of possiblePaths) {
    const cookieFilePath = path.join(profilePath, 'Cookies');
    if (fs.existsSync(cookieFilePath)) {
      console.log(`Found Chrome Cookies file at: ${cookieFilePath}`);
      return cookieFilePath;
    }
  }

  console.error('Chrome Cookies file not found in any of the expected locations');
  return null;
};

const getChromeCookies = async (): Promise<void> => {
  const cookieFilePath = findChromeProfilePath();
  if (!cookieFilePath) {
    return;
  }

  let encryptionKey: Buffer;
  try {
    encryptionKey = await getEncryptionKey();
  } catch (error) {
    console.error('Failed to get encryption key:', error);
    return;
  }

  const db = new sqlite3.Database(cookieFilePath);
  const runAsync = promisify(db.all).bind(db);

  try {
    const cookies: Cookie[] = await runAsync(
      'SELECT host_key, name, encrypted_value FROM cookies'
    );

    console.log(`Total cookies found: ${cookies.length}`);

    cookies.forEach((cookie, index) => {
      try {
        console.log(`\nDecrypting cookie ${index + 1}/${cookies.length}`);
        console.log(`Host: ${cookie.host_key}, Name: ${cookie.name}`);
        const decryptedValue = decryptCookie(cookie.encrypted_value, encryptionKey);
        console.log(`Decrypted Value: ${decryptedValue}`);
      } catch (error) {
        console.error(`Failed to decrypt cookie: ${cookie.name} for domain: ${cookie.host_key}`, error);
      }
    });
  } catch (error) {
    console.error('Error querying the database:', error);
  } finally {
    db.close();
  }
};

getChromeCookies().catch(console.error);

この記事が気に入ったらサポートをしてみませんか?