|
import Foundation |
|
import SQLite3 |
|
import CommonCrypto |
|
import KeychainAccess |
|
import CryptoSwift |
|
|
|
func aes256cbcDecrypt(key: Data, data: Data) -> String? { |
|
let keyData = key |
|
let ivData = " ".data(using: .utf8)! // 16 个空格 |
|
let dataLength = data.count |
|
let bufferSize = dataLength + kCCBlockSizeAES128 |
|
var buffer = [UInt8](repeating: 0, count: bufferSize) |
|
var numBytesDecrypted: size_t = 0 |
|
|
|
let cryptStatus = keyData.withUnsafeBytes { keyBytes in |
|
ivData.withUnsafeBytes { ivBytes in |
|
data.withUnsafeBytes { dataBytes in |
|
CCCrypt(CCOperation(kCCDecrypt), |
|
CCAlgorithm(kCCAlgorithmAES), |
|
CCOptions(kCCOptionPKCS7Padding), |
|
keyBytes, keyData.count, |
|
ivBytes, |
|
dataBytes, dataLength, |
|
&buffer, bufferSize, |
|
&numBytesDecrypted) |
|
} |
|
} |
|
} |
|
|
|
if cryptStatus == kCCSuccess { |
|
return String(bytes: buffer.prefix(Int(numBytesDecrypted)), encoding: .utf8) |
|
} else { |
|
return nil |
|
} |
|
} |
|
|
|
let keychain = Keychain(service: "Chrome Safe Storage") |
|
let passwd = try! keychain.getString("Chrome") |
|
|
|
let salt = Data("saltysalt".utf8) |
|
let length = 16 |
|
let iterations = 1003 |
|
let key = try! PKCS5.PBKDF2(password: Array(passwd!.utf8), salt: salt.bytes, iterations: iterations, keyLength: length, variant: .sha1).calculate() |
|
|
|
let cookieFile = FileManager.default |
|
.homeDirectoryForCurrentUser |
|
.appendingPathComponent("Library/Application Support/Google/Chrome/Default/Cookies") |
|
.path |
|
|
|
var conn: OpaquePointer? |
|
if sqlite3_open(cookieFile, &conn) != SQLITE_OK { |
|
print("Failed to open database") |
|
} |
|
|
|
// NOTE: Chrome uses Win32_FILETIME format |
|
// NOTE: 11644473600 == strftime('%s', '1601-01-01') |
|
let sql = "SELECT host_key, path, is_secure, name, value, encrypted_value, ((expires_utc/1000000)-11644473600) FROM cookies" |
|
|
|
var stmt: OpaquePointer? |
|
if sqlite3_prepare_v2(conn, sql, -1, &stmt, nil) != SQLITE_OK { |
|
print("Failed to prepare statement") |
|
} |
|
|
|
while sqlite3_step(stmt) == SQLITE_ROW { |
|
let hostKey = String(cString: sqlite3_column_text(stmt, 0)) |
|
let path = String(cString: sqlite3_column_text(stmt, 1)) |
|
let isSecure = Int(sqlite3_column_int(stmt, 2)) != 0 |
|
let name = String(cString: sqlite3_column_text(stmt, 3)) |
|
let encryptedValue = sqlite3_column_blob(stmt, 5) |
|
let encryptedValueLength = Int(sqlite3_column_bytes(stmt, 5)) |
|
let expiresUtc = sqlite3_column_double(stmt, 6) |
|
|
|
var value = String(cString: sqlite3_column_text(stmt, 4)) |
|
if let encryptedValue = encryptedValue, encryptedValueLength >= 3, |
|
encryptedValue.load(fromByteOffset: 0, as: UInt8.self) == 118, |
|
encryptedValue.load(fromByteOffset: 1, as: UInt8.self) == 49, |
|
encryptedValue.load(fromByteOffset: 2, as: UInt8.self) == 48 { |
|
let encryptedData = Data(bytes: encryptedValue + 3, count: encryptedValueLength - 3) |
|
let decryptedString = aes256cbcDecrypt(key: Data(key), data: encryptedData) |
|
value = decryptedString ?? "" |
|
} |
|
|
|
let exptime = max(Int(expiresUtc), 0) |
|
let secure = isSecure ? "TRUE" : "FALSE" |
|
|
|
print("\(hostKey)\t\(secure)\t\(path)\t\(secure)\t\(exptime)\t\(name)\t\(value)") |
|
} |
|
|
|
sqlite3_finalize(stmt) |
|
sqlite3_close(conn) |