Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple script to extract (encrypted) cookies out of Chrome OS X cookie store. Usage: ./cookiemonster domain.com
package main
import (
"code.google.com/p/go.crypto/pbkdf2"
"crypto/aes"
"crypto/cipher"
"crypto/sha1"
"database/sql"
"fmt"
"log"
"os"
"os/exec"
"os/user"
"strings"
_ "github.com/mattn/go-sqlite3"
)
// Inpsiration
// http://n8henrie.com/2013/11/use-chromes-cookies-for-easier-downloading-with-python-requests/
// Chromium Mac os_crypt: http://dacort.me/1ynPMgx
var (
salt = "saltysalt"
iv = " "
length = 16
password = ""
iterations = 1003
)
// Cookie - Items for a cookie
type Cookie struct {
Domain string
Key string
Value string
EncryptedValue []byte
}
// DecryptedValue - Get the unencrypted value of a Chrome cookie
func (c *Cookie) DecryptedValue() string {
if c.Value > "" {
return c.Value
}
if len(c.EncryptedValue) > 0 {
encryptedValue := c.EncryptedValue[3:]
return decryptValue(encryptedValue)
}
return ""
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: %s [domain]\n", os.Args[0])
os.Exit(2)
}
func main() {
if len(os.Args) != 2 {
usage()
}
domain := os.Args[1]
password = getPassword()
// TODO: Output in Netscape format
for _, cookie := range getCookies(domain) {
fmt.Printf("%s/%s: %s\n", cookie.Domain, cookie.Key, cookie.DecryptedValue())
}
}
func decryptValue(encryptedValue []byte) string {
key := pbkdf2.Key([]byte(password), []byte(salt), iterations, length, sha1.New)
block, err := aes.NewCipher(key)
if err != nil {
log.Fatal(err)
}
decrypted := make([]byte, len(encryptedValue))
cbc := cipher.NewCBCDecrypter(block, []byte(iv))
cbc.CryptBlocks(decrypted, encryptedValue)
plainText, err := aesStripPadding(decrypted)
if err != nil {
fmt.Println("Error decrypting:", err)
return ""
}
return string(plainText)
}
// In the padding scheme the last <padding length> bytes
// have a value equal to the padding length, always in (1,16]
func aesStripPadding(data []byte) ([]byte, error) {
if len(data)%length != 0 {
return nil, fmt.Errorf("decrypted data block length is not a multiple of %d", length)
}
paddingLen := int(data[len(data)-1])
if paddingLen > 16 {
return nil, fmt.Errorf("invalid last block padding length: %d", paddingLen)
}
return data[:len(data)-paddingLen], nil
}
func getPassword() string {
parts := strings.Fields("security find-generic-password -wga Chrome")
cmd := parts[0]
parts = parts[1:len(parts)]
out, err := exec.Command(cmd, parts...).Output()
if err != nil {
log.Fatal("error finding password ", err)
}
return strings.Trim(string(out), "\n")
}
func getCookies(domain string) (cookies []Cookie) {
usr, _ := user.Current()
cookiesFile := fmt.Sprintf("%s/Library/Application Support/Google/Chrome/Default/Cookies", usr.HomeDir)
db, err := sql.Open("sqlite3", cookiesFile)
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query("SELECT name, value, host_key, encrypted_value FROM cookies WHERE host_key like ?", fmt.Sprintf("%%%s%%", domain))
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
var name, value, hostKey string
var encryptedValue []byte
rows.Scan(&name, &value, &hostKey, &encryptedValue)
cookies = append(cookies, Cookie{hostKey, name, value, encryptedValue})
}
return
}
@DinoChiesa

This comment has been minimized.

Copy link

DinoChiesa commented Sep 14, 2016

Thanks for this, very helpful. The imports need updating now that it's 2016. But this worked great.

import (
    //"code.google.com/p/go.crypto/pbkdf2"
    // "github.com/golang/crypto/pbkdf2"
    "golang.org/x/crypto/pbkdf2"

    "crypto/aes"
    "crypto/cipher"
    "crypto/sha1"
    "database/sql"
    "fmt"
    "log"
    "os"
    "os/exec"
    "os/user"
    "strings"

    _ "github.com/mattn/go-sqlite3"
)
@AddaxSoft

This comment has been minimized.

Copy link

AddaxSoft commented Jul 18, 2017

Anything for windows?

@dolmen

This comment has been minimized.

Copy link

dolmen commented Feb 20, 2018

Please give a license on this code. I would like to reuse it / improve it.

@dacort

This comment has been minimized.

Copy link
Owner Author

dacort commented Apr 30, 2018

@dolmen - Apologies, just saw your comment. License is MIT.

@DukeyToo

This comment has been minimized.

Copy link

DukeyToo commented Oct 10, 2018

https://github.com/zellyn/kooky has a version that works in Safari too. I think they used yours as the basis for the Chrome piece. Example app (same syntax/params as cookiemonster.go):

package main

import (
  "fmt"
  "os/user"
  "os"
  "time"

  "github.com/zellyn/kooky"
)

func usage() {
	fmt.Fprintf(os.Stderr, "usage: %s [domain]\n", os.Args[0])
	os.Exit(2)
}

func main() {
  if len(os.Args) != 2 {
    usage()
  }

  domain := os.Args[1]

  var cookies []*kooky.Cookie
  var err error

  usr, _ := user.Current()

  // safari first
  cookiesFile := fmt.Sprintf("%s/Library/Cookies/Cookies.binarycookies", usr.HomeDir)
  cookies, err = kooky.ReadSafariCookies(cookiesFile, domain, "", time.Time{})
  if len(cookies) == 0 {
    // safari had none, try chrome
    cookiesFile := fmt.Sprintf("%s/Library/Application Support/Google/Chrome/Default/Cookies", usr.HomeDir)
    cookies, err = kooky.ReadChromeCookies(cookiesFile, domain, "", time.Time{})
  }

  if err != nil {
    return
  }

  for _, cookie := range cookies {
    fmt.Printf("%s/%s: %s\n", cookie.Domain, cookie.Name, cookie.Value)
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.